diff options
54 files changed, 2756 insertions, 531 deletions
diff --git a/core/math/basis.cpp b/core/math/basis.cpp index 50299902eb..037378b9d7 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -109,7 +109,7 @@ bool Basis::is_diagonal() const { } bool Basis::is_rotation() const { - return Math::is_equal_approx(determinant(), 1, UNIT_EPSILON) && is_orthogonal(); + return Math::is_equal_approx(determinant(), 1, (real_t)UNIT_EPSILON) && is_orthogonal(); } #ifdef MATH_CHECKS diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h index 575c3f8902..0a3b6ae9da 100644 --- a/core/math/math_funcs.h +++ b/core/math/math_funcs.h @@ -311,20 +311,20 @@ public: static float random(float from, float to); static int random(int from, int to); - static _ALWAYS_INLINE_ bool is_equal_approx(real_t a, real_t b) { + static _ALWAYS_INLINE_ bool is_equal_approx(float a, float b) { // Check for exact equality first, required to handle "infinity" values. if (a == b) { return true; } // Then check for approximate equality. - real_t tolerance = CMP_EPSILON * abs(a); + float tolerance = CMP_EPSILON * abs(a); if (tolerance < CMP_EPSILON) { tolerance = CMP_EPSILON; } return abs(a - b) < tolerance; } - static _ALWAYS_INLINE_ bool is_equal_approx(real_t a, real_t b, real_t tolerance) { + static _ALWAYS_INLINE_ bool is_equal_approx(float a, float b, float tolerance) { // Check for exact equality first, required to handle "infinity" values. if (a == b) { return true; @@ -333,7 +333,33 @@ public: return abs(a - b) < tolerance; } - static _ALWAYS_INLINE_ bool is_zero_approx(real_t s) { + static _ALWAYS_INLINE_ bool is_zero_approx(float s) { + return abs(s) < CMP_EPSILON; + } + + static _ALWAYS_INLINE_ bool is_equal_approx(double a, double b) { + // Check for exact equality first, required to handle "infinity" values. + if (a == b) { + return true; + } + // Then check for approximate equality. + double tolerance = CMP_EPSILON * abs(a); + if (tolerance < CMP_EPSILON) { + tolerance = CMP_EPSILON; + } + return abs(a - b) < tolerance; + } + + static _ALWAYS_INLINE_ bool is_equal_approx(double a, double b, double tolerance) { + // Check for exact equality first, required to handle "infinity" values. + if (a == b) { + return true; + } + // Then check for approximate equality. + return abs(a - b) < tolerance; + } + + static _ALWAYS_INLINE_ bool is_zero_approx(double s) { return abs(s) < CMP_EPSILON; } diff --git a/core/math/quat.cpp b/core/math/quat.cpp index 6f13e04027..3982a0b993 100644 --- a/core/math/quat.cpp +++ b/core/math/quat.cpp @@ -87,7 +87,7 @@ Quat Quat::normalized() const { } bool Quat::is_normalized() const { - return Math::is_equal_approx(length_squared(), 1.0, UNIT_EPSILON); //use less epsilon + return Math::is_equal_approx(length_squared(), 1, (real_t)UNIT_EPSILON); //use less epsilon } Quat Quat::inverse() const { diff --git a/core/math/vector2.cpp b/core/math/vector2.cpp index 5129ed336e..46a08b53ab 100644 --- a/core/math/vector2.cpp +++ b/core/math/vector2.cpp @@ -59,7 +59,7 @@ Vector2 Vector2::normalized() const { bool Vector2::is_normalized() const { // use length_squared() instead of length() to avoid sqrt(), makes it more stringent. - return Math::is_equal_approx(length_squared(), 1.0, UNIT_EPSILON); + return Math::is_equal_approx(length_squared(), 1, (real_t)UNIT_EPSILON); } real_t Vector2::distance_to(const Vector2 &p_vector2) const { diff --git a/core/math/vector3.h b/core/math/vector3.h index b47c3cc916..adfc52566f 100644 --- a/core/math/vector3.h +++ b/core/math/vector3.h @@ -423,7 +423,7 @@ Vector3 Vector3::normalized() const { bool Vector3::is_normalized() const { // use length_squared() instead of length() to avoid sqrt(), makes it more stringent. - return Math::is_equal_approx(length_squared(), 1.0, UNIT_EPSILON); + return Math::is_equal_approx(length_squared(), 1, (real_t)UNIT_EPSILON); } Vector3 Vector3::inverse() const { diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h index 7d33d85cd6..fb791f8c0c 100644 --- a/core/variant/variant_internal.h +++ b/core/variant/variant_internal.h @@ -43,18 +43,21 @@ public: v->type = p_type; switch (p_type) { - case Variant::AABB: - init_aabb(v); + case Variant::STRING: + init_string(v); break; case Variant::TRANSFORM2D: init_transform2d(v); break; + case Variant::AABB: + init_aabb(v); + break; + case Variant::BASIS: + init_basis(v); + break; case Variant::TRANSFORM: init_transform(v); break; - case Variant::STRING: - init_string(v); - break; case Variant::STRING_NAME: init_string_name(v); break; @@ -192,6 +195,10 @@ public: v->type = GetTypeInfo<T>::VARIANT_TYPE; } + // Should be in the same order as Variant::Type for consistency. + // Those primitive and vector types don't need an `init_` method: + // Nil, bool, float, Vector2/i, Rect2/i, Vector3/i, Plane, Quat, Color, RID. + // Object is a special case, handled via `object_assign_null`. _FORCE_INLINE_ static void init_string(Variant *v) { memnew_placement(v->_data._mem, String); v->type = Variant::STRING; diff --git a/doc/classes/JavaScript.xml b/doc/classes/JavaScript.xml index c707a72ee8..e6d74eeb21 100644 --- a/doc/classes/JavaScript.xml +++ b/doc/classes/JavaScript.xml @@ -11,6 +11,24 @@ <link title="Exporting for the Web: Calling JavaScript from script">https://docs.godotengine.org/en/latest/getting_started/workflow/export/exporting_for_web.html#calling-javascript-from-script</link> </tutorials> <methods> + <method name="create_callback"> + <return type="JavaScriptObject"> + </return> + <argument index="0" name="callable" type="Callable"> + </argument> + <description> + Creates a reference to a [Callable] that can be used as a callback by JavaScript. The reference must be kept until the callback happens, or it won't be called at all. See [JavaScriptObject] for usage. + </description> + </method> + <method name="create_object" qualifiers="vararg"> + <return type="Variant"> + </return> + <argument index="0" name="object" type="String"> + </argument> + <description> + Creates a new JavaScript object using the [code]new[/code] constructor. The [code]object[/code] must a valid property of the JavaScript [code]window[/code]. See [JavaScriptObject] for usage. + </description> + </method> <method name="eval"> <return type="Variant"> </return> @@ -23,6 +41,15 @@ If [code]use_global_execution_context[/code] is [code]true[/code], the code will be evaluated in the global execution context. Otherwise, it is evaluated in the execution context of a function within the engine's runtime environment. </description> </method> + <method name="get_interface"> + <return type="JavaScriptObject"> + </return> + <argument index="0" name="interface" type="String"> + </argument> + <description> + Returns an interface to a JavaScript object that can be used by scripts. The [code]interface[/code] must be a valid property of the JavaScript [code]window[/code]. The callback must accept a single [Array] argument, which will contain the JavaScript [code]arguments[/code]. See [JavaScriptObject] for usage. + </description> + </method> </methods> <constants> </constants> diff --git a/doc/classes/JavaScriptObject.xml b/doc/classes/JavaScriptObject.xml new file mode 100644 index 0000000000..a9e9c77e89 --- /dev/null +++ b/doc/classes/JavaScriptObject.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="JavaScriptObject" inherits="Reference" version="4.0"> + <brief_description> + A wrapper class for native JavaScript objects. + </brief_description> + <description> + JavaScriptObject is used to interact with JavaScript objects retrieved or created via [method JavaScript.get_interface], [method JavaScript.create_object], or [method JavaScript.create_callback]. + Example: + [codeblock] + extends Node + + var _my_js_callback = JavaScript.create_callback(self, "myCallback") # This reference must be kept + var console = JavaScript.get_interface("console") + + func _init(): + var buf = JavaScript.create_object("ArrayBuffer", 10) # new ArrayBuffer(10) + print(buf) # prints [JavaScriptObject:OBJECT_ID] + var uint8arr = JavaScript.create_object("Uint8Array", buf) # new Uint8Array(buf) + uint8arr[1] = 255 + prints(uint8arr[1], uint8arr.byteLength) # prints 255 10 + console.log(uint8arr) # prints in browser console "Uint8Array(10) [ 0, 255, 0, 0, 0, 0, 0, 0, 0, 0 ]" + + # Equivalent of JavaScript: Array.from(uint8arr).forEach(myCallback) + JavaScript.get_interface("Array").from(uint8arr).forEach(_my_js_callback) + + func myCallback(args): + # Will be called with the parameters passed to the "forEach" callback + # [0, 0, [JavaScriptObject:1173]] + # [255, 1, [JavaScriptObject:1173]] + # ... + # [0, 9, [JavaScriptObject:1180]] + print(args) + [/codeblock] + Note: Only available in the "HTML5" platform. + </description> + <tutorials> + </tutorials> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/RigidBody3D.xml b/doc/classes/RigidBody3D.xml index 2920c27a7a..2ee8e2697c 100644 --- a/doc/classes/RigidBody3D.xml +++ b/doc/classes/RigidBody3D.xml @@ -103,7 +103,7 @@ [b]Note:[/b] The result of this test is not immediate after moving objects. For performance, list of collisions is updated once per frame and before the physics step. Consider using signals instead. </description> </method> - <method name="get_inverse_inertia_tensor"> + <method name="get_inverse_inertia_tensor" qualifiers="const"> <return type="Basis"> </return> <description> diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml index ff68a017a1..2015b1f1cd 100644 --- a/doc/classes/TileSet.xml +++ b/doc/classes/TileSet.xml @@ -20,7 +20,7 @@ <method name="add_source"> <return type="int"> </return> - <argument index="0" name="atlas_source_id_override" type="TileSetAtlasSource"> + <argument index="0" name="atlas_source_id_override" type="TileSetSource"> </argument> <argument index="1" name="arg1" type="int" default="-1"> </argument> diff --git a/doc/classes/TileSetScenesCollectionSource.xml b/doc/classes/TileSetScenesCollectionSource.xml new file mode 100644 index 0000000000..1a00eb51c0 --- /dev/null +++ b/doc/classes/TileSetScenesCollectionSource.xml @@ -0,0 +1,155 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="TileSetScenesCollectionSource" inherits="TileSetSource" version="4.0"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <methods> + <method name="create_scene_tile"> + <return type="int"> + </return> + <argument index="0" name="packed_scene" type="PackedScene"> + </argument> + <argument index="1" name="id_override" type="int" default="-1"> + </argument> + <description> + </description> + </method> + <method name="get_alternative_tile_id" qualifiers="const"> + <return type="int"> + </return> + <argument index="0" name="atlas_coords" type="Vector2i"> + </argument> + <argument index="1" name="index" type="int"> + </argument> + <description> + </description> + </method> + <method name="get_alternative_tiles_count" qualifiers="const"> + <return type="int"> + </return> + <argument index="0" name="atlas_coords" type="Vector2i"> + </argument> + <description> + </description> + </method> + <method name="get_next_scene_tile_id" qualifiers="const"> + <return type="int"> + </return> + <description> + </description> + </method> + <method name="get_scene_tile_display_placeholder" qualifiers="const"> + <return type="bool"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <description> + </description> + </method> + <method name="get_scene_tile_id"> + <return type="int"> + </return> + <argument index="0" name="index" type="int"> + </argument> + <description> + </description> + </method> + <method name="get_scene_tile_scene" qualifiers="const"> + <return type="PackedScene"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <description> + </description> + </method> + <method name="get_scene_tiles_count"> + <return type="int"> + </return> + <description> + </description> + </method> + <method name="get_tile_id" qualifiers="const"> + <return type="Vector2i"> + </return> + <argument index="0" name="index" type="int"> + </argument> + <description> + </description> + </method> + <method name="get_tiles_count" qualifiers="const"> + <return type="int"> + </return> + <description> + </description> + </method> + <method name="has_alternative_tile" qualifiers="const"> + <return type="bool"> + </return> + <argument index="0" name="atlas_coords" type="Vector2i"> + </argument> + <argument index="1" name="alternative_tile" type="int"> + </argument> + <description> + </description> + </method> + <method name="has_scene_tile_id"> + <return type="bool"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <description> + </description> + </method> + <method name="has_tile" qualifiers="const"> + <return type="bool"> + </return> + <argument index="0" name="atlas_coords" type="Vector2i"> + </argument> + <description> + </description> + </method> + <method name="remove_scene_tile"> + <return type="void"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <description> + </description> + </method> + <method name="set_scene_tile_display_placeholder"> + <return type="void"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <argument index="1" name="display_placeholder" type="bool"> + </argument> + <description> + </description> + </method> + <method name="set_scene_tile_id"> + <return type="void"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <argument index="1" name="new_id" type="int"> + </argument> + <description> + </description> + </method> + <method name="set_scene_tile_scene"> + <return type="void"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <argument index="1" name="packed_scene" type="PackedScene"> + </argument> + <description> + </description> + </method> + </methods> + <constants> + </constants> +</class> diff --git a/drivers/dummy/rasterizer_dummy.h b/drivers/dummy/rasterizer_dummy.h index 1a27c54757..8f20a3472c 100644 --- a/drivers/dummy/rasterizer_dummy.h +++ b/drivers/dummy/rasterizer_dummy.h @@ -554,6 +554,7 @@ public: RID particles_allocate() override { return RID(); } void particles_initialize(RID p_rid) override {} + void particles_set_mode(RID p_particles, RS::ParticlesMode p_mode) override {} void particles_emit(RID p_particles, const Transform &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) override {} void particles_set_emitting(RID p_particles, bool p_emitting) override {} void particles_set_amount(RID p_particles, int p_amount) override {} diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 641d823d8f..ba39ce3aed 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -85,10 +85,10 @@ void ViewportRotationControl::_notification(int p_what) { axis_menu_options.clear(); axis_menu_options.push_back(Node3DEditorViewport::VIEW_RIGHT); axis_menu_options.push_back(Node3DEditorViewport::VIEW_TOP); - axis_menu_options.push_back(Node3DEditorViewport::VIEW_FRONT); + axis_menu_options.push_back(Node3DEditorViewport::VIEW_REAR); axis_menu_options.push_back(Node3DEditorViewport::VIEW_LEFT); axis_menu_options.push_back(Node3DEditorViewport::VIEW_BOTTOM); - axis_menu_options.push_back(Node3DEditorViewport::VIEW_REAR); + axis_menu_options.push_back(Node3DEditorViewport::VIEW_FRONT); axis_colors.clear(); axis_colors.push_back(get_theme_color("axis_x_color", "Editor")); diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index 476e9a072b..551d1c027a 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -513,7 +513,7 @@ void SpriteFramesEditor::_animation_select() { if (frames->has_animation(edited_anim)) { double value = anim_speed->get_line_edit()->get_text().to_float(); - if (!Math::is_equal_approx(value, frames->get_animation_speed(edited_anim))) { + if (!Math::is_equal_approx(value, (double)frames->get_animation_speed(edited_anim))) { _animation_fps_changed(value); } } diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp index 8c5bb56287..6e82951d3d 100644 --- a/editor/plugins/tiles/tile_atlas_view.cpp +++ b/editor/plugins/tiles/tile_atlas_view.cpp @@ -195,9 +195,9 @@ void TileAtlasView::_base_tiles_root_control_gui_input(const Ref<InputEvent> &p_ if (mm.is_valid()) { Transform2D xform = base_tiles_drawing_root->get_transform().affine_inverse(); Vector2i coords = get_atlas_tile_coords_at_pos(xform.xform(mm->get_position())); - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { coords = tile_set_atlas_source->get_tile_at_coords(coords); - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { base_tiles_root_control->set_tooltip(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: 0"), source_id, coords)); } } @@ -215,7 +215,7 @@ void TileAtlasView::_draw_base_tiles() { for (int x = 0; x < grid_size.x; x++) { for (int y = 0; y < grid_size.y; y++) { Vector2i coords = Vector2i(x, y); - if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { Rect2i rect = Rect2i(texture_region_size * coords + margins, texture_region_size); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); } @@ -229,17 +229,23 @@ void TileAtlasView::_draw_base_tiles() { rect.set_end(Vector2i(texture->get_size().x, margins.y)); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); // Bottom - rect.position = Vector2i(0, margins.y + (grid_size.y * texture_region_size.y)); - rect.set_end(texture->get_size()); - base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + int bottom_border = margins.y + (grid_size.y * texture_region_size.y); + if (bottom_border < texture->get_size().y) { + rect.position = Vector2i(0, bottom_border); + rect.set_end(texture->get_size()); + base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + } // Left rect.position = Vector2i(0, margins.y); rect.set_end(Vector2i(margins.x, margins.y + (grid_size.y * texture_region_size.y))); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); // Right. - rect.position = Vector2i(margins.x + (grid_size.x * texture_region_size.x), margins.y); - rect.set_end(Vector2i(texture->get_size().x, margins.y + (grid_size.y * texture_region_size.y))); - base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + int right_border = margins.x + (grid_size.x * texture_region_size.x); + if (right_border < texture->get_size().x) { + rect.position = Vector2i(right_border, margins.y); + rect.set_end(Vector2i(texture->get_size().x, margins.y + (grid_size.y * texture_region_size.y))); + base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + } // Draw actual tiles, using their properties (modulation, etc...) for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { @@ -249,7 +255,7 @@ void TileAtlasView::_draw_base_tiles() { Vector2i offset_pos = (margins + (atlas_coords * texture_region_size) + tile_set_atlas_source->get_tile_texture_region(atlas_coords).size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0)); // Draw the tile. - TileSetAtlasPluginRendering::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0); + TileSetPluginAtlasRendering::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0); } } } @@ -268,7 +274,7 @@ void TileAtlasView::_draw_base_tiles_texture_grid() { for (int y = 0; y < grid_size.y; y++) { Vector2i origin = margins + (Vector2i(x, y) * (texture_region_size + separation)); Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y)); - if (base_tile_coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) { if (base_tile_coords == Vector2i(x, y)) { // Draw existing tile. Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(base_tile_coords); @@ -299,7 +305,7 @@ void TileAtlasView::_draw_base_tiles_dark() { Vector2i origin = margins + (Vector2i(x, y) * (texture_region_size + separation)); Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y)); - if (base_tile_coords == TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (base_tile_coords == TileSetSource::INVALID_ATLAS_COORDS) { // Draw the grid. base_tiles_dark->draw_rect(Rect2i(origin, texture_region_size), Color(0.0, 0.0, 0.0, 0.5), true); } @@ -331,7 +337,7 @@ void TileAtlasView::_alternative_tiles_root_control_gui_input(const Ref<InputEve Vector3i coords3 = get_alternative_tile_at_pos(xform.xform(mm->get_position())); Vector2i coords = Vector2i(coords3.x, coords3.y); int alternative_id = coords3.z; - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && alternative_id != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS && alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE) { alternative_tiles_root_control->set_tooltip(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: %d"), source_id, coords, alternative_id)); } } @@ -364,7 +370,7 @@ void TileAtlasView::_draw_alternatives() { } // Draw the tile. - TileSetAtlasPluginRendering::draw_tile(alternatives_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, alternative_id); + TileSetPluginAtlasRendering::draw_tile(alternatives_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, alternative_id); // Increment the x position. current_pos.x += transposed ? texture_region.size.y : texture_region.size.x; @@ -464,7 +470,7 @@ Vector2i TileAtlasView::get_atlas_tile_coords_at_pos(const Vector2 p_pos) const return ret; } - return TileSetAtlasSource::INVALID_ATLAS_COORDS; + return TileSetSource::INVALID_ATLAS_COORDS; } void TileAtlasView::_update_alternative_tiles_rect_cache() { @@ -506,7 +512,7 @@ Vector3i TileAtlasView::get_alternative_tile_at_pos(const Vector2 p_pos) const { } } - return Vector3i(TileSetAtlasSource::INVALID_ATLAS_COORDS.x, TileSetAtlasSource::INVALID_ATLAS_COORDS.y, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + return Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE); } Rect2i TileAtlasView::get_alternative_tile_rect(const Vector2i p_coords, int p_alternative_tile) { diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index 0b674bec39..d2665a3a2b 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -166,7 +166,7 @@ void TileDataTerrainsEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform Vector<String> components = String(p_property).split("/", true); if (components[0] == "terrain_mode" || components[0] == "terrain" || components[0] == "terrains_peering_bit") { - TileSetAtlasPluginTerrain::draw_terrains(p_canvas_item, p_transform, p_tile_set, tile_data); + TileSetPluginAtlasTerrain::draw_terrains(p_canvas_item, p_transform, p_tile_set, tile_data); } } diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index 73640a78c2..80ba396936 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -32,6 +32,7 @@ #include "tiles_editor_plugin.h" +#include "editor/editor_resource_preview.h" #include "editor/editor_scale.h" #include "editor/plugins/canvas_item_editor_plugin.h" @@ -55,7 +56,7 @@ void TileMapEditorTilesPlugin::_notification(int p_what) { picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons")); erase_button->set_icon(get_theme_icon("Eraser", "EditorIcons")); - missing_texture_texture = get_theme_icon("TileSet", "EditorIcons"); + missing_atlas_texture_icon = get_theme_icon("TileSet", "EditorIcons"); break; case NOTIFICATION_VISIBILITY_CHANGED: _stop_dragging(); @@ -64,9 +65,8 @@ void TileMapEditorTilesPlugin::_notification(int p_what) { void TileMapEditorTilesPlugin::tile_set_changed() { _update_fix_selected_and_hovered(); - _update_bottom_panel(); _update_tile_set_sources_list(); - _update_atlas_view(); + _update_bottom_panel(); } void TileMapEditorTilesPlugin::_on_random_tile_checkbox_toggled(bool p_pressed) { @@ -146,19 +146,38 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { for (int i = 0; i < tile_set->get_source_count(); i++) { int source_id = tile_set->get_source_id(i); - // TODO: handle with virtual functions TileSetSource *source = *tile_set->get_source(source_id); + + Ref<Texture2D> texture; + String item_text; + + // Atlas source. TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { - Ref<Texture2D> texture = atlas_source->get_texture(); + texture = atlas_source->get_texture(); if (texture.is_valid()) { - sources_list->add_item(vformat("%s - (id:%d)", texture->get_path().get_file(), source_id), texture); + item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id); } else { - sources_list->add_item(vformat("No Texture Atlas Source - (id:%d)", source_id), missing_texture_texture); + item_text = vformat("No Texture Atlas Source (id:%d)", source_id); } - } else { - sources_list->add_item(vformat("Unknown Type Source - (id:%d)", source_id), missing_texture_texture); } + + // Scene collection source. + TileSetScenesCollectionSource *scene_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); + if (scene_collection_source) { + texture = get_theme_icon("PackedScene", "EditorIcons"); + item_text = vformat(TTR("Scene Collection Source (id:%d)"), source_id); + } + + // Use default if not valid. + if (item_text.is_empty()) { + item_text = vformat(TTR("Unknown Type Source (id:%d)"), source_id); + } + if (!texture.is_valid()) { + texture = missing_atlas_texture_icon; + } + + sources_list->add_item(item_text, texture); sources_list->set_item_metadata(i, source_id); } @@ -176,7 +195,7 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { TilesEditor::get_singleton()->set_atlas_sources_lists_current(sources_list->get_current()); } -void TileMapEditorTilesPlugin::_update_atlas_view() { +void TileMapEditorTilesPlugin::_update_bottom_panel() { // Update the atlas display. TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { @@ -185,39 +204,161 @@ void TileMapEditorTilesPlugin::_update_atlas_view() { Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - tile_atlas_view->hide(); return; } int source_index = sources_list->get_current(); if (source_index >= 0 && source_index < sources_list->get_item_count()) { + atlas_sources_split_container->show(); + missing_source_label->hide(); + int source_id = sources_list->get_item_metadata(source_index); TileSetSource *source = *tile_set->get_source(source_id); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); + if (atlas_source) { - tile_atlas_view->set_atlas_source(*tile_map->get_tileset(), atlas_source, source_id); tile_atlas_view->show(); + scene_tiles_list->hide(); + invalid_source_label->hide(); + _update_atlas_view(); + } else if (scenes_collection_source) { + tile_atlas_view->hide(); + scene_tiles_list->show(); + invalid_source_label->hide(); + _update_scenes_collection_view(); + } else { + tile_atlas_view->hide(); + scene_tiles_list->hide(); + invalid_source_label->show(); } } else { + atlas_sources_split_container->hide(); + missing_source_label->show(); + tile_atlas_view->hide(); + scene_tiles_list->hide(); + invalid_source_label->hide(); } +} - // Synchronize atlas view. - TilesEditor::get_singleton()->synchronize_atlas_view(tile_atlas_view); +void TileMapEditorTilesPlugin::_update_atlas_view() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + int source_id = sources_list->get_item_metadata(sources_list->get_current()); + TileSetSource *source = *tile_set->get_source(source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + ERR_FAIL_COND(!atlas_source); + + tile_atlas_view->set_atlas_source(*tile_map->get_tileset(), atlas_source, source_id); + TilesEditor::get_singleton()->synchronize_atlas_view(tile_atlas_view); tile_atlas_control->update(); } -void TileMapEditorTilesPlugin::_update_bottom_panel() { +void TileMapEditorTilesPlugin::_update_scenes_collection_view() { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { return; } + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + int source_id = sources_list->get_item_metadata(sources_list->get_current()); + TileSetSource *source = *tile_set->get_source(source_id); + TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); + ERR_FAIL_COND(!scenes_collection_source); + + // Clear the list. + scene_tiles_list->clear(); + + // Rebuild the list. + for (int i = 0; i < scenes_collection_source->get_scene_tiles_count(); i++) { + int scene_id = scenes_collection_source->get_scene_tile_id(i); + + Ref<PackedScene> scene = scenes_collection_source->get_scene_tile_scene(scene_id); + + int item_index = 0; + if (scene.is_valid()) { + item_index = scene_tiles_list->add_item(vformat("%s (path:%s id:%d)", scene->get_path().get_file().get_basename(), scene->get_path(), scene_id)); + Variant udata = i; + EditorResourcePreview::get_singleton()->queue_edited_resource_preview(scene, this, "_scene_thumbnail_done", udata); + } else { + item_index = scene_tiles_list->add_item(TTR("Tile with Invalid Scene"), get_theme_icon("PackedScene", "EditorIcons")); + } + scene_tiles_list->set_item_metadata(item_index, scene_id); - // Update the tabs. - missing_source_label->set_visible(tile_set.is_valid() && tile_set->get_source_count() == 0); - atlas_sources_split_container->set_visible(tile_set.is_valid() && tile_set->get_source_count() > 0); + // Check if in selection. + if (tile_set_selection.has(TileMapCell(source_id, Vector2i(), scene_id))) { + scene_tiles_list->select(item_index, false); + } + } + + // Icon size update. + int int_size = int(EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size")) * EDSCALE; + scene_tiles_list->set_fixed_icon_size(Vector2(int_size, int_size)); +} + +void TileMapEditorTilesPlugin::_scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud) { + int index = p_ud; + + if (index >= 0 && index < scene_tiles_list->get_item_count()) { + scene_tiles_list->set_item_icon(index, p_preview); + } +} + +void TileMapEditorTilesPlugin::_scenes_list_multi_selected(int p_index, bool p_selected) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + // Add or remove the Tile form the selection. + int scene_id = scene_tiles_list->get_item_metadata(p_index); + int source_id = sources_list->get_item_metadata(sources_list->get_current()); + TileSetSource *source = *tile_set->get_source(source_id); + TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); + ERR_FAIL_COND(!scenes_collection_source); + + TileMapCell selected = TileMapCell(source_id, Vector2i(), scene_id); + + // Clear the selection if shift is not pressed. + if (!Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + tile_set_selection.clear(); + } + + if (p_selected) { + tile_set_selection.insert(selected); + } else { + if (tile_set_selection.has(selected)) { + tile_set_selection.erase(selected); + } + } + + _update_selection_pattern_from_tileset_selection(); +} + +void TileMapEditorTilesPlugin::_scenes_list_nothing_selected() { + scene_tiles_list->deselect_all(); + tile_set_selection.clear(); + tile_map_selection.clear(); + selection_pattern->clear(); + _update_selection_pattern_from_tileset_selection(); } bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { @@ -257,7 +398,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p if (!tile_map_selection.is_empty()) { undo_redo->create_action(TTR("Delete tiles")); for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", E->get(), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + undo_redo->add_do_method(tile_map, "set_cell", E->get(), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); undo_redo->add_undo_method(tile_map, "set_cell", E->get(), tile_map->get_cell_source_id(E->get()), tile_map->get_cell_atlas_coords(E->get()), tile_map->get_cell_alternative_tile(E->get())); } undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection()); @@ -288,7 +429,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p if (!tile_map_selection.is_empty()) { undo_redo->create_action(TTR("Delete tiles")); for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", E->get(), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + undo_redo->add_do_method(tile_map, "set_cell", E->get(), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); undo_redo->add_undo_method(tile_map, "set_cell", E->get(), tile_map->get_cell_source_id(E->get()), tile_map->get_cell_atlas_coords(E->get()), tile_map->get_cell_alternative_tile(E->get())); } undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection()); @@ -364,7 +505,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { Vector2i coords = E->get(); drag_modified.insert(coords, TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords))); - tile_map->set_cell(coords, -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + tile_map->set_cell(coords, -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); } } else { // Select tiles @@ -467,7 +608,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over } } - // handle the preview of the tiles to be placed. + // Handle the preview of the tiles to be placed. if (is_visible_in_tree() && has_mouse) { // Only if the tilemap editor is opened and the viewport is hovered. Map<Vector2i, TileMapCell> preview; Rect2i drawn_grid_rect; @@ -583,11 +724,10 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over // Draw the preview. for (Map<Vector2i, TileMapCell>::Element *E = preview.front(); E; E = E->next()) { + Vector2i size = tile_set->get_tile_size(); + Vector2 position = tile_map->map_to_world(E->key()) - size / 2; + Rect2 cell_region = xform.xform(Rect2(position, size)); if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { - Vector2i size = tile_set->get_tile_size(); - Vector2 position = tile_map->map_to_world(E->key()) - size / 2; - Rect2 cell_region = xform.xform(Rect2(position, size)); - tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0, 0.5), true); } else { if (tile_set->has_source(E->get().source_id)) { @@ -629,12 +769,10 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over // Draw the tile. p_overlay->draw_texture_rect_region(atlas_source->get_texture(), dest_rect, source_rect, modulate * Color(1.0, 1.0, 1.0, 0.5), transpose, tile_set->is_uv_clipping()); + } else { + tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0, 0.5), true); } } else { - Vector2i size = tile_set->get_tile_size(); - Vector2 position = tile_map->map_to_world(E->key()) - size / 2; - Rect2 cell_region = xform.xform(Rect2(position, size)); - tile_set->draw_tile_shape(p_overlay, cell_region, Color(0.0, 0.0, 0.0, 0.5), true); } } @@ -669,7 +807,9 @@ TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(const TileMapPattern *p_ TileSetSource *source = *tile_set->get_source(source_id); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { - sum += Object::cast_to<TileData>(atlas_source->get_tile_data(atlas_coords, alternative_tile))->get_probability(); + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(atlas_coords, alternative_tile)); + ERR_FAIL_COND_V(!tile_data, TileMapCell()); + sum += tile_data->get_probability(); } else { sum += 1.0; } @@ -698,7 +838,7 @@ TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(const TileMapPattern *p_ return TileMapCell(); } -Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2i p_to_mouse_pos) { +Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos) { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { return Map<Vector2i, TileMapCell>(); @@ -711,7 +851,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_ // Get or create the pattern. TileMapPattern erase_pattern; - erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; Map<Vector2i, TileMapCell> output; @@ -763,7 +903,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start // Get or create the pattern. TileMapPattern erase_pattern; - erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; // Compute the offset to align things to the bottom or right. @@ -814,7 +954,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i // Get or create the pattern. TileMapPattern erase_pattern; - erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; Map<Vector2i, TileMapCell> output; @@ -1090,8 +1230,8 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { hovered_tile.source_id = -1; - hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS); - hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_selection.clear(); tile_map_selection.clear(); selection_pattern->clear(); @@ -1101,8 +1241,8 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { hovered_tile.source_id = -1; - hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS); - hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_selection.clear(); tile_map_selection.clear(); selection_pattern->clear(); @@ -1112,8 +1252,8 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { int source_index = sources_list->get_current(); if (source_index < 0 || source_index >= sources_list->get_item_count()) { hovered_tile.source_id = -1; - hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS); - hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_selection.clear(); tile_map_selection.clear(); selection_pattern->clear(); @@ -1128,8 +1268,8 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { !tile_set->get_source(hovered_tile.source_id)->has_tile(hovered_tile.get_atlas_coords()) || !tile_set->get_source(hovered_tile.source_id)->has_alternative_tile(hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile)) { hovered_tile.source_id = -1; - hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS); - hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; } // Selection if needed. @@ -1187,6 +1327,7 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection( per_source[E->get().source_id].push_back(&(E->get())); } + int vertical_offset = 0; for (Map<int, List<const TileMapCell *>>::Element *E_source = per_source.front(); E_source; E_source = E_source->next()) { // Per source. List<const TileMapCell *> unorganized; @@ -1199,24 +1340,21 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection( // Organize using coordinates. for (List<const TileMapCell *>::Element *E_cell = E_source->get().front(); E_cell; E_cell = E_cell->next()) { const TileMapCell *current = E_cell->get(); - if (organized_pattern.has(current->get_atlas_coords())) { - if (current->alternative_tile < organized_pattern[current->get_atlas_coords()]->alternative_tile) { - unorganized.push_back(organized_pattern[current->get_atlas_coords()]); - organized_pattern[current->get_atlas_coords()] = current; - } else { - unorganized.push_back(current); - } - } else { + if (current->alternative_tile == 0) { organized_pattern[current->get_atlas_coords()] = current; + } else { + unorganized.push_back(current); } } // Compute the encompassing rect for the organized pattern. Map<Vector2i, const TileMapCell *>::Element *E_cell = organized_pattern.front(); - encompassing_rect_coords = Rect2i(E_cell->key(), Vector2i(1, 1)); - for (; E_cell; E_cell = E_cell->next()) { - encompassing_rect_coords.expand_to(E_cell->key() + Vector2i(1, 1)); - encompassing_rect_coords.expand_to(E_cell->key()); + if (E_cell) { + encompassing_rect_coords = Rect2i(E_cell->key(), Vector2i(1, 1)); + for (; E_cell; E_cell = E_cell->next()) { + encompassing_rect_coords.expand_to(E_cell->key() + Vector2i(1, 1)); + encompassing_rect_coords.expand_to(E_cell->key()); + } } } else { // Add everything unorganized. @@ -1227,12 +1365,15 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection( // Now add everything to the output pattern. for (Map<Vector2i, const TileMapCell *>::Element *E_cell = organized_pattern.front(); E_cell; E_cell = E_cell->next()) { - selection_pattern->set_cell(E_cell->key() - encompassing_rect_coords.position, E_cell->get()->source_id, E_cell->get()->get_atlas_coords(), E_cell->get()->alternative_tile); + selection_pattern->set_cell(E_cell->key() - encompassing_rect_coords.position + Vector2i(0, vertical_offset), E_cell->get()->source_id, E_cell->get()->get_atlas_coords(), E_cell->get()->alternative_tile); } Vector2i organized_size = selection_pattern->get_size(); + int unorganized_index = 0; for (List<const TileMapCell *>::Element *E_cell = unorganized.front(); E_cell; E_cell = E_cell->next()) { - selection_pattern->set_cell(Vector2(organized_size.x, 0), E_cell->get()->source_id, E_cell->get()->get_atlas_coords(), E_cell->get()->alternative_tile); + selection_pattern->set_cell(Vector2(organized_size.x + unorganized_index, vertical_offset), E_cell->get()->source_id, E_cell->get()->get_atlas_coords(), E_cell->get()->alternative_tile); + unorganized_index++; } + vertical_offset += MAX(organized_size.y, 1); } CanvasItemEditor::get_singleton()->update_viewport(); } @@ -1246,7 +1387,7 @@ void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern( tile_set_selection.insert(TileMapCell(selection_pattern->get_cell_source_id(coords), selection_pattern->get_cell_atlas_coords(coords), selection_pattern->get_cell_alternative_tile(coords))); } } - _update_atlas_view(); + _update_bottom_panel(); } void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { @@ -1283,7 +1424,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { } // Draw the hovered tile. - if (hovered_tile.get_atlas_coords() != TileSetAtlasSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0 && !tile_set_dragging_selection) { + if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0 && !tile_set_dragging_selection) { tile_atlas_control->draw_rect(atlas->get_tile_texture_region(hovered_tile.get_atlas_coords()), Color(1.0, 1.0, 1.0), false); } @@ -1299,7 +1440,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { for (int x = region.position.x; x < region.get_end().x; x++) { for (int y = region.position.y; y < region.get_end().y; y++) { Vector2i tile = atlas->get_tile_at_coords(Vector2i(x, y)); - if (tile != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (tile != TileSetSource::INVALID_ATLAS_COORDS) { to_draw.insert(tile); } } @@ -1313,8 +1454,8 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { void TileMapEditorTilesPlugin::_tile_atlas_control_mouse_exited() { hovered_tile.source_id = -1; - hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS); - hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_dragging_selection = false; tile_atlas_control->update(); } @@ -1347,12 +1488,12 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven // Update the hovered tile hovered_tile.source_id = source_id; - hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS); - hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { coords = atlas->get_tile_at_coords(coords); - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { hovered_tile.set_atlas_coords(coords); hovered_tile.alternative_tile = 0; } @@ -1373,7 +1514,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven tile_set_selection.clear(); } - if (hovered_tile.get_atlas_coords() != TileSetAtlasSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0) { + if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0) { if (mb->is_shift_pressed() && tile_set_selection.has(TileMapCell(source_id, hovered_tile.get_atlas_coords(), 0))) { tile_set_selection.erase(TileMapCell(source_id, hovered_tile.get_atlas_coords(), 0)); } else { @@ -1389,18 +1530,18 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven // Compute the covered area. Vector2i start_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_set_drag_start_mouse_pos); Vector2i end_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); - if (start_tile != TileSetAtlasSource::INVALID_ATLAS_COORDS && end_tile != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (start_tile != TileSetSource::INVALID_ATLAS_COORDS && end_tile != TileSetSource::INVALID_ATLAS_COORDS) { Rect2i region = Rect2i(start_tile, end_tile - start_tile).abs(); region.size += Vector2i(1, 1); // To update the selection, we copy the selected/not selected status of the tiles we drag from. Vector2i start_coords = atlas->get_tile_at_coords(start_tile); - if (mb->is_shift_pressed() && start_coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && !tile_set_selection.has(TileMapCell(source_id, start_coords, 0))) { + if (mb->is_shift_pressed() && start_coords != TileSetSource::INVALID_ATLAS_COORDS && !tile_set_selection.has(TileMapCell(source_id, start_coords, 0))) { // Remove from the selection. for (int x = region.position.x; x < region.get_end().x; x++) { for (int y = region.position.y; y < region.get_end().y; y++) { Vector2i tile_coords = atlas->get_tile_at_coords(Vector2i(x, y)); - if (tile_coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && tile_set_selection.has(TileMapCell(source_id, tile_coords, 0))) { + if (tile_coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_selection.has(TileMapCell(source_id, tile_coords, 0))) { tile_set_selection.erase(TileMapCell(source_id, tile_coords, 0)); } } @@ -1410,7 +1551,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven for (int x = region.position.x; x < region.get_end().x; x++) { for (int y = region.position.y; y < region.get_end().y; y++) { Vector2i tile_coords = atlas->get_tile_at_coords(Vector2i(x, y)); - if (tile_coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (tile_coords != TileSetSource::INVALID_ATLAS_COORDS) { tile_set_selection.insert(TileMapCell(source_id, tile_coords, 0)); } } @@ -1453,7 +1594,7 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_draw() { // Draw the selection. for (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) { - if (E->get().source_id == source_id && E->get().get_atlas_coords() != TileSetAtlasSource::INVALID_ATLAS_COORDS && E->get().alternative_tile > 0) { + if (E->get().source_id == source_id && E->get().get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && E->get().alternative_tile > 0) { Rect2i rect = tile_atlas_view->get_alternative_tile_rect(E->get().get_atlas_coords(), E->get().alternative_tile); if (rect != Rect2i()) { alternative_tiles_control->draw_rect(rect, Color(0.2, 0.2, 1.0), false); @@ -1462,7 +1603,7 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_draw() { } // Draw hovered tile. - if (hovered_tile.get_atlas_coords() != TileSetAtlasSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile > 0) { + if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile > 0) { Rect2i rect = tile_atlas_view->get_alternative_tile_rect(hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile); if (rect != Rect2i()) { alternative_tiles_control->draw_rect(rect, Color(1.0, 1.0, 1.0), false); @@ -1472,8 +1613,8 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_draw() { void TileMapEditorTilesPlugin::_tile_alternatives_control_mouse_exited() { hovered_tile.source_id = -1; - hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS); - hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_dragging_selection = false; alternative_tiles_control->update(); } @@ -1506,12 +1647,12 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input(const Ref<In // Update the hovered tile hovered_tile.source_id = source_id; - hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS); - hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; Vector3i alternative_coords = tile_atlas_view->get_alternative_tile_at_pos(alternative_tiles_control->get_local_mouse_position()); Vector2i coords = Vector2i(alternative_coords.x, alternative_coords.y); int alternative = alternative_coords.z; - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && alternative != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS && alternative != TileSetSource::INVALID_TILE_ALTERNATIVE) { hovered_tile.set_atlas_coords(coords); hovered_tile.alternative_tile = alternative; } @@ -1530,7 +1671,7 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input(const Ref<In tile_set_selection.clear(); } - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && alternative != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS && alternative != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) { if (mb->is_shift_pressed() && tile_set_selection.has(TileMapCell(source_id, hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile))) { tile_set_selection.erase(TileMapCell(source_id, hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile)); } else { @@ -1565,13 +1706,14 @@ TypedArray<Vector2i> TileMapEditorTilesPlugin::_get_tile_map_selection() const { void TileMapEditorTilesPlugin::edit(ObjectID p_tile_map_id) { tile_map_id = p_tile_map_id; - // Clean the selection. + // Clear the selection. tile_set_selection.clear(); tile_map_selection.clear(); selection_pattern->clear(); } void TileMapEditorTilesPlugin::_bind_methods() { + ClassDB::bind_method(D_METHOD("_scene_thumbnail_done"), &TileMapEditorTilesPlugin::_scene_thumbnail_done); ClassDB::bind_method(D_METHOD("_set_tile_map_selection", "selection"), &TileMapEditorTilesPlugin::_set_tile_map_selection); ClassDB::bind_method(D_METHOD("_get_tile_map_selection"), &TileMapEditorTilesPlugin::_get_tile_map_selection); } @@ -1718,20 +1860,20 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { sources_list->set_h_size_flags(SIZE_EXPAND_FILL); sources_list->set_stretch_ratio(0.25); sources_list->set_custom_minimum_size(Size2i(70, 0) * EDSCALE); + sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_fix_selected_and_hovered).unbind(1)); - sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_atlas_view).unbind(1)); + sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_bottom_panel).unbind(1)); sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_sources_lists_current)); sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_atlas_sources_list), varray(sources_list)); - //sources_list->set_drag_forwarding(this); atlas_sources_split_container->add_child(sources_list); + // Tile atlas source. tile_atlas_view = memnew(TileAtlasView); tile_atlas_view->set_h_size_flags(SIZE_EXPAND_FILL); tile_atlas_view->set_v_size_flags(SIZE_EXPAND_FILL); tile_atlas_view->set_texture_grid_visible(false); tile_atlas_view->set_tile_shape_grid_visible(false); tile_atlas_view->connect("transform_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_view_transform)); - //tile_atlas_view->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_atlas_view), varray(tile_atlas_view)); atlas_sources_split_container->add_child(tile_atlas_view); tile_atlas_control = memnew(Control); @@ -1746,6 +1888,27 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { alternative_tiles_control->connect("gui_input", callable_mp(this, &TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input)); tile_atlas_view->add_control_over_alternative_tiles(alternative_tiles_control); + // Scenes collection source. + scene_tiles_list = memnew(ItemList); + scene_tiles_list->set_h_size_flags(SIZE_EXPAND_FILL); + scene_tiles_list->set_v_size_flags(SIZE_EXPAND_FILL); + scene_tiles_list->set_drag_forwarding(this); + scene_tiles_list->set_select_mode(ItemList::SELECT_MULTI); + scene_tiles_list->connect("multi_selected", callable_mp(this, &TileMapEditorTilesPlugin::_scenes_list_multi_selected)); + scene_tiles_list->connect("nothing_selected", callable_mp(this, &TileMapEditorTilesPlugin::_scenes_list_nothing_selected)); + scene_tiles_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); + atlas_sources_split_container->add_child(scene_tiles_list); + + // Invalid source label. + invalid_source_label = memnew(Label); + invalid_source_label->set_text(TTR("Invalid source selected.")); + invalid_source_label->set_h_size_flags(SIZE_EXPAND_FILL); + invalid_source_label->set_v_size_flags(SIZE_EXPAND_FILL); + invalid_source_label->set_align(Label::ALIGN_CENTER); + invalid_source_label->set_valign(Label::VALIGN_CENTER); + invalid_source_label->hide(); + atlas_sources_split_container->add_child(invalid_source_label); + _update_bottom_panel(); } @@ -2771,8 +2934,8 @@ void TileMapEditorTerrainsPlugin::_update_terrains_cache() { TileMapCell empty_cell; empty_cell.source_id = -1; - empty_cell.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS); - empty_cell.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + empty_cell.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + empty_cell.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; per_terrain_terrains_tile_patterns_tiles[i][empty_pattern].insert(empty_cell); } } diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h index f780686b82..a1fb9af3ef 100644 --- a/editor/plugins/tiles/tile_map_editor.h +++ b/editor/plugins/tiles/tile_map_editor.h @@ -58,7 +58,7 @@ private: ObjectID tile_map_id; virtual void edit(ObjectID p_tile_map_id) override; - // Toolbar. + ///// Toolbar ///// HBoxContainer *toolbar; Ref<ButtonGroup> tool_buttons_group; @@ -84,7 +84,7 @@ private: void _update_toolbar(); - // Tilemap editing. + ///// Tilemap editing. ///// bool has_mouse = false; void _mouse_exited_viewport(); @@ -105,12 +105,12 @@ private: Map<Vector2i, TileMapCell> drag_modified; TileMapCell _pick_random_tile(const TileMapPattern *p_pattern); - Map<Vector2i, TileMapCell> _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2i p_to_mouse_pos); - Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_mouse_pos, Vector2i p_end_mouse_pos); + Map<Vector2i, TileMapCell> _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos); + Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell); Map<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous); void _stop_dragging(); - // Selection system. + ///// Selection system. ///// Set<Vector2i> tile_map_selection; TileMapPattern *tile_map_clipboard = memnew(TileMapPattern); TileMapPattern *selection_pattern = memnew(TileMapPattern); @@ -124,22 +124,24 @@ private: void _update_tileset_selection_from_selection_pattern(); void _update_fix_selected_and_hovered(); - // Bottom panel. - bool tile_set_dragging_selection = false; - Vector2i tile_set_drag_start_mouse_pos; - + ///// Bottom panel. ////. Label *missing_source_label; - HSplitContainer *atlas_sources_split_container; + Label *invalid_source_label; ItemList *sources_list; - TileAtlasView *tile_atlas_view; - Ref<Texture2D> missing_texture_texture; + + Ref<Texture2D> missing_atlas_texture_icon; void _update_tile_set_sources_list(); - void _update_atlas_view(); void _update_bottom_panel(); + // Atlas sources. TileMapCell hovered_tile; + TileAtlasView *tile_atlas_view; + HSplitContainer *atlas_sources_split_container; + + bool tile_set_dragging_selection = false; + Vector2i tile_set_drag_start_mouse_pos; Control *tile_atlas_control; void _tile_atlas_control_mouse_exited(); @@ -151,6 +153,16 @@ private: void _tile_alternatives_control_mouse_exited(); void _tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event); + void _update_atlas_view(); + + // Scenes collection sources. + ItemList *scene_tiles_list; + + void _update_scenes_collection_view(); + void _scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud); + void _scenes_list_multi_selected(int p_index, bool p_selected); + void _scenes_list_nothing_selected(); + // Update callback virtual void tile_set_changed() override; diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 324e429592..b6e1a318cb 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -53,10 +53,10 @@ void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::set_id(int p_id) { if (source_id == p_id) { return; } - ERR_FAIL_COND_MSG(tile_set->has_source(p_id), vformat("Cannot change TileSet atlas source ID. Another atlas source exists with id %d.", p_id)); + ERR_FAIL_COND_MSG(tile_set->has_source(p_id), vformat("Cannot change TileSet Atlas Source ID. Another source exists with id %d.", p_id)); int previous_source = source_id; - source_id = p_id; // source_id must be updated before, because it's used by the atlas source list update. + source_id = p_id; // source_id must be updated before, because it's used by the source list update. tile_set->set_source_id(previous_source, p_id); emit_signal("changed", "id"); } @@ -126,7 +126,7 @@ void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::edit(Ref<TileSet> } // -- Proxy object used by the tile inspector -- -bool TileSetAtlasSourceEditor::TileProxyObject::_set(const StringName &p_name, const Variant &p_value) { +bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_name, const Variant &p_value) { if (!tile_set_atlas_source) { return false; } @@ -152,9 +152,9 @@ bool TileSetAtlasSourceEditor::TileProxyObject::_set(const StringName &p_name, c return true; } else if (alternative == 0 && p_name == "size_in_atlas") { Vector2i as_vector2i = Vector2i(p_value); - ERR_FAIL_COND_V(!tile_set_atlas_source->can_move_tile_in_atlas(coords, TileSetAtlasSource::INVALID_ATLAS_COORDS, as_vector2i), false); + ERR_FAIL_COND_V(!tile_set_atlas_source->can_move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i), false); - tile_set_atlas_source->move_tile_in_atlas(coords, TileSetAtlasSource::INVALID_ATLAS_COORDS, as_vector2i); + tile_set_atlas_source->move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i); emit_signal("changed", "size_in_atlas"); return true; } else if (alternative > 0 && p_name == "alternative_id") { @@ -197,7 +197,7 @@ bool TileSetAtlasSourceEditor::TileProxyObject::_set(const StringName &p_name, c return any_valid; } -bool TileSetAtlasSourceEditor::TileProxyObject::_get(const StringName &p_name, Variant &r_ret) const { +bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_name, Variant &r_ret) const { if (!tile_set_atlas_source) { return false; } @@ -237,7 +237,7 @@ bool TileSetAtlasSourceEditor::TileProxyObject::_get(const StringName &p_name, V return false; } -void TileSetAtlasSourceEditor::TileProxyObject::_get_property_list(List<PropertyInfo> *p_list) const { +void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<PropertyInfo> *p_list) const { if (!tile_set_atlas_source) { return; } @@ -310,12 +310,11 @@ void TileSetAtlasSourceEditor::TileProxyObject::_get_property_list(List<Property } } -void TileSetAtlasSourceEditor::TileProxyObject::edit(TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id, Set<TileSelection> p_tiles) { +void TileSetAtlasSourceEditor::AtlasTileProxyObject::edit(TileSetAtlasSource *p_tile_set_atlas_source, Set<TileSelection> p_tiles) { ERR_FAIL_COND(!p_tile_set_atlas_source); - ERR_FAIL_COND(p_source_id < 0); ERR_FAIL_COND(p_tiles.is_empty()); for (Set<TileSelection>::Element *E = p_tiles.front(); E; E = E->next()) { - ERR_FAIL_COND(E->get().tile == TileSetAtlasSource::INVALID_ATLAS_COORDS); + ERR_FAIL_COND(E->get().tile == TileSetSource::INVALID_ATLAS_COORDS); ERR_FAIL_COND(E->get().alternative < 0); } @@ -333,7 +332,6 @@ void TileSetAtlasSourceEditor::TileProxyObject::edit(TileSetAtlasSource *p_tile_ } tile_set_atlas_source = p_tile_set_atlas_source; - source_id = p_source_id; tiles = Set<TileSelection>(p_tiles); // Connect to changes. @@ -352,7 +350,7 @@ void TileSetAtlasSourceEditor::TileProxyObject::edit(TileSetAtlasSource *p_tile_ notify_property_list_changed(); } -void TileSetAtlasSourceEditor::TileProxyObject::_bind_methods() { +void TileSetAtlasSourceEditor::AtlasTileProxyObject::_bind_methods() { ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what"))); } @@ -391,12 +389,12 @@ void TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles() { // Fix hovered. if (!tile_set_atlas_source->has_tile(hovered_base_tile_coords)) { - hovered_base_tile_coords = TileSetAtlasSource::INVALID_ATLAS_COORDS; + hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS; } Vector2i coords = Vector2i(hovered_alternative_tile_coords.x, hovered_alternative_tile_coords.y); int alternative = hovered_alternative_tile_coords.z; if (!tile_set_atlas_source->has_tile(coords) || !tile_set_atlas_source->has_alternative_tile(coords, alternative)) { - hovered_alternative_tile_coords = Vector3i(TileSetAtlasSource::INVALID_ATLAS_COORDS.x, TileSetAtlasSource::INVALID_ATLAS_COORDS.y, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE); } } @@ -405,7 +403,7 @@ void TileSetAtlasSourceEditor::_update_tile_inspector() { // Update the proxy object. if (has_atlas_tile_selected) { - tile_proxy_object->edit(tile_set_atlas_source, tile_set_atlas_source_id, selection); + tile_proxy_object->edit(tile_set_atlas_source, selection); } // Update visibility. @@ -486,7 +484,7 @@ void TileSetAtlasSourceEditor::_update_toolbar() { } void TileSetAtlasSourceEditor::_tile_atlas_control_mouse_exited() { - hovered_base_tile_coords = TileSetAtlasSource::INVALID_ATLAS_COORDS; + hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS; tile_atlas_control->update(); tile_atlas_control_unscaled->update(); tile_atlas_view->update(); @@ -514,7 +512,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven if (selection.size() == 1) { // Change the cursor depending on the hovered thing. TileSelection selected = selection.front()->get(); - if (selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS && selected.alternative == 0) { + if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) { Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position(); Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile); Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile); @@ -560,7 +558,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven Vector<Point2i> line = Geometry2D::bresenham_line(last_base_tiles_coords, new_base_tiles_coords); for (int i = 0; i < line.size(); i++) { - if (tile_set_atlas_source->get_tile_at_coords(line[i]) == TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (tile_set_atlas_source->get_tile_at_coords(line[i]) == TileSetSource::INVALID_ATLAS_COORDS) { tile_set_atlas_source->create_tile(line[i]); drag_modified_tiles.insert(line[i]); } @@ -576,7 +574,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven Vector<Point2i> line = Geometry2D::bresenham_line(last_base_tiles_coords, new_base_tiles_coords); for (int i = 0; i < line.size(); i++) { Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(line[i]); - if (base_tile_coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) { drag_modified_tiles.insert(base_tile_coords); } } @@ -662,17 +660,17 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven // Remove a first tile. Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { coords = tile_set_atlas_source->get_tile_at_coords(coords); } - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { drag_modified_tiles.insert(coords); } } else { if (mb->is_shift_pressed()) { // Create a big tile. Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos); - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { // Setup the dragging info, only if we start on an empty tile. drag_type = DRAG_TYPE_CREATE_BIG_TILE; drag_start_mouse_pos = mouse_local_pos; @@ -692,7 +690,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven // Create a first tile if needed. Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { tile_set_atlas_source->create_tile(coords); drag_modified_tiles.insert(coords); } @@ -710,7 +708,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven if (mb->is_shift_pressed()) { // Create a big tile. Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos); - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { // Setup the dragging info, only if we start on an empty tile. drag_type = DRAG_TYPE_CREATE_BIG_TILE; drag_start_mouse_pos = mouse_local_pos; @@ -732,7 +730,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven drag_type = DRAG_TYPE_NONE; if (selection.size() == 1) { TileSelection selected = selection.front()->get(); - if (selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS && selected.alternative == 0) { + if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) { Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile); Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile); Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom(); @@ -771,17 +769,17 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven // Selecting then dragging a tile. if (drag_type == DRAG_TYPE_NONE) { - TileSelection selected = { TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE }; + TileSelection selected = { TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE }; Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos); - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { coords = tile_set_atlas_source->get_tile_at_coords(coords); - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { selected = { coords, 0 }; } } bool shift = mb->is_shift_pressed(); - if (!shift && selection.size() == 1 && selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS && selection.has(selected)) { + if (!shift && selection.size() == 1 && selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selection.has(selected)) { // Start move dragging. drag_type = DRAG_TYPE_MOVE_TILE; drag_start_mouse_pos = mouse_local_pos; @@ -812,13 +810,13 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven // Right click pressed. TileSelection selected = { tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos), 0 }; - if (selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) { selected.tile = tile_set_atlas_source->get_tile_at_coords(selected.tile); } // Set the selection if needed. if (selection.size() <= 1) { - if (selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) { undo_redo->create_action(TTR("Select tiles")); undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array()); selection.clear(); @@ -831,15 +829,15 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven } // Pops up the correct menu, depending on whether we have a tile or not. - if (selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS && selection.has(selected)) { + if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selection.has(selected)) { // We have a tile. menu_option_coords = selected.tile; menu_option_alternative = 0; base_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i())); - } else if (hovered_base_tile_coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + } else if (hovered_base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) { // We don't have a tile, but can create one. menu_option_coords = hovered_base_tile_coords; - menu_option_alternative = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + menu_option_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE; empty_base_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i())); } } else { @@ -902,7 +900,7 @@ void TileSetAtlasSourceEditor::_end_dragging() { for (int x = area.get_position().x; x < area.get_end().x; x++) { for (int y = area.get_position().y; y < area.get_end().y; y++) { Vector2i coords = Vector2i(x, y); - if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { undo_redo->add_do_method(tile_set_atlas_source, "create_tile", coords); undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", coords); } @@ -923,7 +921,7 @@ void TileSetAtlasSourceEditor::_end_dragging() { for (int x = area.get_position().x; x < area.get_end().x; x++) { for (int y = area.get_position().y; y < area.get_end().y; y++) { Vector2i coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y)); - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { to_delete.insert(coords); } } @@ -964,8 +962,8 @@ void TileSetAtlasSourceEditor::_end_dragging() { case DRAG_TYPE_RECT_SELECT: { Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); - ERR_FAIL_COND(start_base_tiles_coords == TileSetAtlasSource::INVALID_ATLAS_COORDS); - ERR_FAIL_COND(new_base_tiles_coords == TileSetAtlasSource::INVALID_ATLAS_COORDS); + ERR_FAIL_COND(start_base_tiles_coords == TileSetSource::INVALID_ATLAS_COORDS); + ERR_FAIL_COND(new_base_tiles_coords == TileSetSource::INVALID_ATLAS_COORDS); Rect2i region = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs(); region.size += Vector2i(1, 1); @@ -977,7 +975,7 @@ void TileSetAtlasSourceEditor::_end_dragging() { bool add_to_selection = true; if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { Vector2i coords = tile_set_atlas_source->get_tile_at_coords(start_base_tiles_coords); - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { if (selection.has({ coords, 0 })) { add_to_selection = false; } @@ -991,7 +989,7 @@ void TileSetAtlasSourceEditor::_end_dragging() { for (int y = region.position.y; y < region.get_end().y; y++) { Vector2i coords = Vector2i(x, y); coords = tile_set_atlas_source->get_tile_at_coords(coords); - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { if (add_to_selection && !selection.has({ coords, 0 })) { selection.insert({ coords, 0 }); } else if (!add_to_selection && selection.has({ coords, 0 })) { @@ -1243,7 +1241,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { for (int x = area.get_position().x; x < area.get_end().x; x++) { for (int y = area.get_position().y; y < area.get_end().y; y++) { Vector2i coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y)); - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { to_paint.insert(coords); } } @@ -1266,7 +1264,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { for (int x = area.get_position().x; x < area.get_end().x; x++) { for (int y = area.get_position().y; y < area.get_end().y; y++) { Vector2i coords = Vector2i(x, y); - if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { Vector2i origin = margins + (coords * (tile_size + separation)); tile_atlas_control->draw_rect(Rect2i(origin, tile_size), Color(1.0, 1.0, 1.0), false); } @@ -1290,7 +1288,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); if (hovered_base_tile_coords.x >= 0 && hovered_base_tile_coords.y >= 0 && hovered_base_tile_coords.x < grid_size.x && hovered_base_tile_coords.y < grid_size.y) { Vector2i hovered_tile = tile_set_atlas_source->get_tile_at_coords(hovered_base_tile_coords); - if (hovered_tile != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (hovered_tile != TileSetSource::INVALID_ATLAS_COORDS) { // Draw existing hovered tile. tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(hovered_tile), Color(1.0, 1.0, 1.0), false); } else { @@ -1349,7 +1347,7 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input(const Ref<In selection.clear(); TileSelection selected = { Vector2i(tile.x, tile.y), int(tile.z) }; - if (selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) { selection.insert(selected); } @@ -1364,7 +1362,7 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input(const Ref<In selection.clear(); TileSelection selected = { Vector2i(tile.x, tile.y), int(tile.z) }; - if (selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) { selection.insert(selected); } @@ -1387,7 +1385,7 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input(const Ref<In } void TileSetAtlasSourceEditor::_tile_alternatives_control_mouse_exited() { - hovered_alternative_tile_coords = Vector3i(TileSetAtlasSource::INVALID_ATLAS_COORDS.x, TileSetAtlasSource::INVALID_ATLAS_COORDS.y, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE); tile_atlas_control->update(); tile_atlas_control_unscaled->update(); alternative_tiles_control->update(); @@ -1399,7 +1397,7 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_draw() { if (tools_button_group->get_pressed_button() == tool_select_button) { // Draw hovered tile. Vector2i coords = Vector2(hovered_alternative_tile_coords.x, hovered_alternative_tile_coords.y); - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { Rect2i rect = tile_atlas_view->get_alternative_tile_rect(coords, hovered_alternative_tile_coords.z); if (rect != Rect2i()) { alternative_tiles_control->draw_rect(rect, Color(1.0, 1.0, 1.0), false); @@ -1441,7 +1439,7 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo #define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property)); - TileProxyObject *tile_data = Object::cast_to<TileProxyObject>(p_edited); + AtlasTileProxyObject *tile_data = Object::cast_to<AtlasTileProxyObject>(p_edited); if (tile_data) { Vector<String> components = String(p_property).split("/", true, 2); if (components.size() == 2 && components[1] == "shapes_count") { @@ -1518,7 +1516,7 @@ void TileSetAtlasSourceEditor::_auto_create_tiles() { for (int x = 0; x < grid_size.x; x++) { // Check if we have a tile at the coord Vector2i coords = Vector2i(x, y); - if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { // Check if the texture is empty at the given coords. Rect2i region = Rect2i(margins + (coords * (texture_region_size + separation)), texture_region_size); bool is_opaque = false; @@ -1673,7 +1671,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tile_inspector_label->hide(); middle_vbox_container->add_child(tile_inspector_label); - tile_proxy_object = memnew(TileProxyObject(this)); + tile_proxy_object = memnew(AtlasTileProxyObject(this)); tile_proxy_object->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view).unbind(1)); tile_inspector = memnew(EditorInspector); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h index ff68aa8288..70f2cdbe01 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.h +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -45,8 +45,8 @@ class TileSetAtlasSourceEditor : public HBoxContainer { private: // A class to store which tiles are selected. struct TileSelection { - Vector2i tile = TileSetAtlasSource::INVALID_ATLAS_COORDS; - int alternative = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + Vector2i tile = TileSetSource::INVALID_ATLAS_COORDS; + int alternative = TileSetSource::INVALID_TILE_ALTERNATIVE; bool operator<(const TileSelection &p_other) const { if (tile == p_other.tile) { @@ -80,14 +80,13 @@ private: }; // -- Proxy object for a tile, needed by the inspector -- - class TileProxyObject : public Object { - GDCLASS(TileProxyObject, Object); + class AtlasTileProxyObject : public Object { + GDCLASS(AtlasTileProxyObject, Object); private: TileSetAtlasSourceEditor *tiles_set_atlas_source_editor; TileSetAtlasSource *tile_set_atlas_source = nullptr; - int source_id; Set<TileSelection> tiles = Set<TileSelection>(); protected: @@ -99,10 +98,10 @@ private: public: // Update the proxyed object. - void edit(TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id = -1, Set<TileSelection> p_tiles = Set<TileSelection>()); + void edit(TileSetAtlasSource *p_tile_set_atlas_source, Set<TileSelection> p_tiles = Set<TileSelection>()); - TileProxyObject(TileSetAtlasSourceEditor *p_tiles_editor_source_tab) { - tiles_set_atlas_source_editor = p_tiles_editor_source_tab; + AtlasTileProxyObject(TileSetAtlasSourceEditor *p_tiles_set_atlas_source_editor) { + tiles_set_atlas_source_editor = p_tiles_set_atlas_source_editor; } }; @@ -115,7 +114,7 @@ private: bool tile_set_atlas_source_changed_needs_update = false; // -- Inspector -- - TileProxyObject *tile_proxy_object; + AtlasTileProxyObject *tile_proxy_object; Label *tile_inspector_label; EditorInspector *tile_inspector; String selected_property; @@ -175,7 +174,7 @@ private: ADVANCED_AUTO_REMOVE_TILES, }; Vector2i menu_option_coords; - int menu_option_alternative = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + int menu_option_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE; void _menu_option(int p_option); // Tool buttons. @@ -198,7 +197,7 @@ private: Array _get_selection_as_array(); // A control on the tile atlas to draw and handle input events. - Vector2i hovered_base_tile_coords = TileSetAtlasSource::INVALID_ATLAS_COORDS; + Vector2i hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS; PopupMenu *base_tile_popup_menu; PopupMenu *empty_base_tile_popup_menu; @@ -213,7 +212,7 @@ private: void _tile_atlas_view_transform_changed(); // A control over the alternative tiles. - Vector3i hovered_alternative_tile_coords = Vector3i(TileSetAtlasSource::INVALID_ATLAS_COORDS.x, TileSetAtlasSource::INVALID_ATLAS_COORDS.y, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + Vector3i hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE); PopupMenu *alternative_tile_popup_menu; Control *alternative_tiles_control; diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp index 5573fae670..05ebe408a5 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -140,20 +140,39 @@ void TileSetEditor::_update_atlas_sources_list(int force_selected_id) { for (int i = 0; i < tile_set->get_source_count(); i++) { int source_id = tile_set->get_source_id(i); - // TODO: handle with virtual functions TileSetSource *source = *tile_set->get_source(source_id); + + Ref<Texture2D> texture; + String item_text; + + // Atlas source. TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { - Ref<Texture2D> texture = atlas_source->get_texture(); + texture = atlas_source->get_texture(); if (texture.is_valid()) { - sources_list->add_item(vformat("%s - (id:%d)", texture->get_path().get_file(), source_id), texture); + item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id); } else { - sources_list->add_item(vformat("No texture atlas source - (id:%d)", source_id), missing_texture_texture); + item_text = vformat(TTR("No Texture Atlas Source (id:%d)"), source_id); } - } else { - sources_list->add_item(vformat("Unknown type source - (id:%d)", source_id), missing_texture_texture); } - sources_list->set_item_metadata(sources_list->get_item_count() - 1, source_id); + + // Scene collection source. + TileSetScenesCollectionSource *scene_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); + if (scene_collection_source) { + texture = get_theme_icon("PackedScene", "EditorIcons"); + item_text = vformat(TTR("Scene Collection Source (id:%d)"), source_id); + } + + // Use default if not valid. + if (item_text.is_empty()) { + item_text = vformat(TTR("Unknown Type Source (id:%d)"), source_id); + } + if (!texture.is_valid()) { + texture = missing_texture_texture; + } + + sources_list->add_item(item_text, texture); + sources_list->set_item_metadata(i, source_id); } // Set again the current selected item if needed. @@ -193,35 +212,63 @@ void TileSetEditor::_source_selected(int p_source_index) { if (p_source_index >= 0) { int source_id = sources_list->get_item_metadata(p_source_index); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id)); + TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(*tile_set->get_source(source_id)); if (atlas_source) { - tile_set_atlas_source_editor->edit(*tile_set, atlas_source, source_id); no_source_selected_label->hide(); + tile_set_atlas_source_editor->edit(*tile_set, atlas_source, source_id); tile_set_atlas_source_editor->show(); + tile_set_scenes_collection_source_editor->hide(); + } else if (scenes_collection_source) { + no_source_selected_label->hide(); + tile_set_atlas_source_editor->hide(); + tile_set_scenes_collection_source_editor->edit(*tile_set, scenes_collection_source, source_id); + tile_set_scenes_collection_source_editor->show(); } else { no_source_selected_label->show(); tile_set_atlas_source_editor->hide(); + tile_set_scenes_collection_source_editor->hide(); } } else { no_source_selected_label->show(); tile_set_atlas_source_editor->hide(); + tile_set_scenes_collection_source_editor->hide(); } } -void TileSetEditor::_source_add_pressed() { +void TileSetEditor::_source_add_id_pressed(int p_id_pressed) { ERR_FAIL_COND(!tile_set.is_valid()); - int source_id = tile_set->get_next_source_id(); + switch (p_id_pressed) { + case 0: { + int source_id = tile_set->get_next_source_id(); - Ref<TileSetAtlasSource> atlas_source = memnew(TileSetAtlasSource); + Ref<TileSetAtlasSource> atlas_source = memnew(TileSetAtlasSource); - // Add a new source. - undo_redo->create_action(TTR("Add atlas source")); - undo_redo->add_do_method(*tile_set, "add_source", atlas_source, source_id); - undo_redo->add_do_method(*atlas_source, "set_texture_region_size", tile_set->get_tile_size()); - undo_redo->add_undo_method(*tile_set, "remove_source", source_id); - undo_redo->commit_action(); + // Add a new source. + undo_redo->create_action(TTR("Add atlas source")); + undo_redo->add_do_method(*tile_set, "add_source", atlas_source, source_id); + undo_redo->add_do_method(*atlas_source, "set_texture_region_size", tile_set->get_tile_size()); + undo_redo->add_undo_method(*tile_set, "remove_source", source_id); + undo_redo->commit_action(); + + _update_atlas_sources_list(source_id); + } break; + case 1: { + int source_id = tile_set->get_next_source_id(); + + Ref<TileSetScenesCollectionSource> scene_collection_source = memnew(TileSetScenesCollectionSource); - _update_atlas_sources_list(source_id); + // Add a new source. + undo_redo->create_action(TTR("Add atlas source")); + undo_redo->add_do_method(*tile_set, "add_source", scene_collection_source, source_id); + undo_redo->add_undo_method(*tile_set, "remove_source", source_id); + undo_redo->commit_action(); + + _update_atlas_sources_list(source_id); + } break; + default: + ERR_FAIL(); + } } void TileSetEditor::_source_delete_pressed() { @@ -434,6 +481,7 @@ void TileSetEditor::edit(Ref<TileSet> p_tile_set) { } tile_set_atlas_source_editor->hide(); + tile_set_scenes_collection_source_editor->hide(); no_source_selected_label->show(); } @@ -464,6 +512,7 @@ TileSetEditor::TileSetEditor() { sources_list->connect("item_selected", callable_mp(this, &TileSetEditor::_source_selected)); sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_sources_lists_current)); sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_atlas_sources_list), varray(sources_list)); + sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); sources_list->set_drag_forwarding(this); split_container_left_side->add_child(sources_list); @@ -477,11 +526,19 @@ TileSetEditor::TileSetEditor() { sources_delete_button->connect("pressed", callable_mp(this, &TileSetEditor::_source_delete_pressed)); sources_bottom_actions->add_child(sources_delete_button); - sources_add_button = memnew(Button); + sources_add_button = memnew(MenuButton); sources_add_button->set_flat(true); - sources_add_button->connect("pressed", callable_mp(this, &TileSetEditor::_source_add_pressed)); + sources_add_button->get_popup()->add_item(TTR("Atlas")); + sources_add_button->get_popup()->add_item(TTR("Scenes Collection")); + sources_add_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_source_add_id_pressed)); sources_bottom_actions->add_child(sources_add_button); + // Right side container. + VBoxContainer *split_container_right_side = memnew(VBoxContainer); + split_container_right_side->set_h_size_flags(SIZE_EXPAND_FILL); + split_container_right_side->set_v_size_flags(SIZE_EXPAND_FILL); + split_container->add_child(split_container_right_side); + // No source selected. no_source_selected_label = memnew(Label); no_source_selected_label->set_text(TTR("No TileSet source selected. Select or create a TileSet source.")); @@ -489,16 +546,24 @@ TileSetEditor::TileSetEditor() { no_source_selected_label->set_v_size_flags(SIZE_EXPAND_FILL); no_source_selected_label->set_align(Label::ALIGN_CENTER); no_source_selected_label->set_valign(Label::VALIGN_CENTER); - split_container->add_child(no_source_selected_label); + split_container_right_side->add_child(no_source_selected_label); // Atlases editor. tile_set_atlas_source_editor = memnew(TileSetAtlasSourceEditor); tile_set_atlas_source_editor->set_h_size_flags(SIZE_EXPAND_FILL); tile_set_atlas_source_editor->set_v_size_flags(SIZE_EXPAND_FILL); tile_set_atlas_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_atlas_sources_list)); - split_container->add_child(tile_set_atlas_source_editor); + split_container_right_side->add_child(tile_set_atlas_source_editor); tile_set_atlas_source_editor->hide(); + // Scenes collection editor. + tile_set_scenes_collection_source_editor = memnew(TileSetScenesCollectionSourceEditor); + tile_set_scenes_collection_source_editor->set_h_size_flags(SIZE_EXPAND_FILL); + tile_set_scenes_collection_source_editor->set_v_size_flags(SIZE_EXPAND_FILL); + tile_set_scenes_collection_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_atlas_sources_list)); + split_container_right_side->add_child(tile_set_scenes_collection_source_editor); + tile_set_scenes_collection_source_editor->hide(); + // Registers UndoRedo inspector callback. EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetEditor::_undo_redo_inspector_callback)); } diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h index c4aebb40a2..d508c04319 100644 --- a/editor/plugins/tiles/tile_set_editor.h +++ b/editor/plugins/tiles/tile_set_editor.h @@ -35,6 +35,7 @@ #include "scene/resources/tile_set.h" #include "tile_data_editors.h" #include "tile_set_atlas_source_editor.h" +#include "tile_set_scenes_collection_source_editor.h" class TileSetEditor : public VBoxContainer { GDCLASS(TileSetEditor, VBoxContainer); @@ -47,6 +48,7 @@ private: Label *no_source_selected_label; TileSetAtlasSourceEditor *tile_set_atlas_source_editor; + TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor; UndoRedo *undo_redo = EditorNode::get_undo_redo(); @@ -64,11 +66,11 @@ private: // -- Sources management -- Button *sources_delete_button; - Button *sources_add_button; + MenuButton *sources_add_button; ItemList *sources_list; Ref<Texture2D> missing_texture_texture; void _source_selected(int p_source_index); - void _source_add_pressed(); + void _source_add_id_pressed(int p_id_pressed); void _source_delete_pressed(); void _tile_set_changed(); diff --git a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp new file mode 100644 index 0000000000..568d4ca8d7 --- /dev/null +++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp @@ -0,0 +1,511 @@ +/*************************************************************************/ +/* tile_set_scenes_collection_source_editor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "tile_set_scenes_collection_source_editor.h" + +#include "editor/editor_resource_preview.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" + +#include "scene/gui/item_list.h" + +#include "core/core_string_names.h" + +void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::set_id(int p_id) { + ERR_FAIL_COND(p_id < 0); + if (source_id == p_id) { + return; + } + ERR_FAIL_COND_MSG(tile_set->has_source(p_id), vformat("Cannot change TileSet Scenes Collection source ID. Another TileSet source exists with id %d.", p_id)); + + int previous_source = source_id; + source_id = p_id; // source_id must be updated before, because it's used by the source list update. + tile_set->set_source_id(previous_source, p_id); + emit_signal("changed", "id"); +} + +int TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::get_id() { + return source_id; +} + +bool TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_set(const StringName &p_name, const Variant &p_value) { + bool valid = false; + tile_set_scenes_collection_source->set(p_name, p_value, &valid); + if (valid) { + emit_signal("changed", String(p_name).utf8().get_data()); + } + return valid; +} + +bool TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_get(const StringName &p_name, Variant &r_ret) const { + if (!tile_set_scenes_collection_source) { + return false; + } + bool valid = false; + r_ret = tile_set_scenes_collection_source->get(p_name, &valid); + return valid; +} + +void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_bind_methods() { + // -- Shape and layout -- + ClassDB::bind_method(D_METHOD("set_id", "id"), &TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::set_id); + ClassDB::bind_method(D_METHOD("get_id"), &TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::get_id); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "id"), "set_id", "get_id"); + + ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what"))); +} + +void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id) { + ERR_FAIL_COND(!p_tile_set.is_valid()); + ERR_FAIL_COND(!p_tile_set_scenes_collection_source); + ERR_FAIL_COND(p_source_id < 0); + ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_scenes_collection_source); + + // Disconnect to changes. + if (tile_set_scenes_collection_source) { + tile_set_scenes_collection_source->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed)); + } + + tile_set = p_tile_set; + tile_set_scenes_collection_source = p_tile_set_scenes_collection_source; + source_id = p_source_id; + + // Connect to changes. + if (tile_set_scenes_collection_source) { + if (!tile_set_scenes_collection_source->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) { + tile_set_scenes_collection_source->connect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed)); + } + } + + notify_property_list_changed(); +} + +// -- Proxy object used by the tile inspector -- +bool TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_set(const StringName &p_name, const Variant &p_value) { + if (!tile_set_scenes_collection_source) { + return false; + } + + if (p_name == "id") { + int as_int = int(p_value); + ERR_FAIL_COND_V(as_int < 0, false); + ERR_FAIL_COND_V(tile_set_scenes_collection_source->has_scene_tile_id(as_int), false); + tile_set_scenes_collection_source->set_scene_tile_id(scene_id, as_int); + scene_id = as_int; + emit_signal("changed", "id"); + for (int i = 0; i < tile_set_scenes_collection_source_editor->scene_tiles_list->get_item_count(); i++) { + if (int(tile_set_scenes_collection_source_editor->scene_tiles_list->get_item_metadata(i)) == scene_id) { + tile_set_scenes_collection_source_editor->scene_tiles_list->select(i); + break; + } + } + return true; + } else if (p_name == "scene") { + tile_set_scenes_collection_source->set_scene_tile_scene(scene_id, p_value); + emit_signal("changed", "scene"); + return true; + } else if (p_name == "display_placeholder") { + tile_set_scenes_collection_source->set_scene_tile_display_placeholder(scene_id, p_value); + emit_signal("changed", "display_placeholder"); + return true; + } + + return false; +} + +bool TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_get(const StringName &p_name, Variant &r_ret) const { + if (!tile_set_scenes_collection_source) { + return false; + } + + if (p_name == "id") { + r_ret = scene_id; + return true; + } else if (p_name == "scene") { + r_ret = tile_set_scenes_collection_source->get_scene_tile_scene(scene_id); + return true; + } else if (p_name == "display_placeholder") { + r_ret = tile_set_scenes_collection_source->get_scene_tile_display_placeholder(scene_id); + return true; + } + + return false; +} + +void TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_get_property_list(List<PropertyInfo> *p_list) const { + if (!tile_set_scenes_collection_source) { + return; + } + + p_list->push_back(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::OBJECT, "scene", PROPERTY_HINT_RESOURCE_TYPE, "PackedScene")); + p_list->push_back(PropertyInfo(Variant::BOOL, "display_placeholder", PROPERTY_HINT_NONE, "")); +} + +void TileSetScenesCollectionSourceEditor::SceneTileProxyObject::edit(TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_scene_id) { + ERR_FAIL_COND(!p_tile_set_scenes_collection_source); + ERR_FAIL_COND(!p_tile_set_scenes_collection_source->has_scene_tile_id(p_scene_id)); + + tile_set_scenes_collection_source = p_tile_set_scenes_collection_source; + scene_id = p_scene_id; + + notify_property_list_changed(); +} + +void TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_bind_methods() { + ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what"))); +} + +void TileSetScenesCollectionSourceEditor::_scenes_collection_source_proxy_object_changed(String p_what) { + if (p_what == "id") { + emit_signal("source_id_changed", scenes_collection_source_proxy_object->get_id()); + } +} + +void TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed() { + tile_set_scenes_collection_source_changed_needs_update = true; +} + +void TileSetScenesCollectionSourceEditor::_scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud) { + int index = p_ud; + + if (index >= 0 && index < scene_tiles_list->get_item_count()) { + scene_tiles_list->set_item_icon(index, p_preview); + } +} + +void TileSetScenesCollectionSourceEditor::_scenes_list_item_activated(int p_index) { + Ref<PackedScene> packed_scene = tile_set_scenes_collection_source->get_scene_tile_scene(scene_tiles_list->get_item_metadata(p_index)); + if (packed_scene.is_valid()) { + EditorNode::get_singleton()->open_request(packed_scene->get_path()); + } +} + +void TileSetScenesCollectionSourceEditor::_source_add_pressed() { + int scene_id = tile_set_scenes_collection_source->get_next_scene_tile_id(); + undo_redo->create_action(TTR("Add a Scene Tile")); + undo_redo->add_do_method(tile_set_scenes_collection_source, "create_scene_tile", Ref<PackedScene>(), scene_id); + undo_redo->add_undo_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id); + undo_redo->commit_action(); + _update_scenes_list(); + _update_action_buttons(); + _update_tile_inspector(); +} + +void TileSetScenesCollectionSourceEditor::_source_delete_pressed() { + Vector<int> selected_indices = scene_tiles_list->get_selected_items(); + ERR_FAIL_COND(selected_indices.size() <= 0); + int scene_id = scene_tiles_list->get_item_metadata(selected_indices[0]); + + undo_redo->create_action(TTR("Remove a Scene Tile")); + undo_redo->add_do_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id); + undo_redo->add_undo_method(tile_set_scenes_collection_source, "create_scene_tile", tile_set_scenes_collection_source->get_scene_tile_scene(scene_id), scene_id); + undo_redo->commit_action(); + _update_scenes_list(); + _update_action_buttons(); + _update_tile_inspector(); +} + +void TileSetScenesCollectionSourceEditor::_update_source_inspector() { + // Update the proxy object. + scenes_collection_source_proxy_object->edit(tile_set, tile_set_scenes_collection_source, tile_set_source_id); +} + +void TileSetScenesCollectionSourceEditor::_update_tile_inspector() { + Vector<int> selected_indices = scene_tiles_list->get_selected_items(); + bool has_atlas_tile_selected = (selected_indices.size() > 0); + + // Update the proxy object. + if (has_atlas_tile_selected) { + int scene_id = scene_tiles_list->get_item_metadata(selected_indices[0]); + tile_proxy_object->edit(tile_set_scenes_collection_source, scene_id); + } + + // Update visibility. + tile_inspector_label->set_visible(has_atlas_tile_selected); + tile_inspector->set_visible(has_atlas_tile_selected); +} + +void TileSetScenesCollectionSourceEditor::_update_action_buttons() { + Vector<int> selected_indices = scene_tiles_list->get_selected_items(); + scene_tile_delete_button->set_disabled(selected_indices.size() <= 0); +} + +void TileSetScenesCollectionSourceEditor::_update_scenes_list() { + if (!tile_set_scenes_collection_source) { + return; + } + + // Get the previously selected id. + Vector<int> selected_indices = scene_tiles_list->get_selected_items(); + int old_selected_scene_id = (selected_indices.size() > 0) ? int(scene_tiles_list->get_item_metadata(selected_indices[0])) : -1; + + // Clear the list. + scene_tiles_list->clear(); + + // Rebuild the list. + int to_reselect = -1; + for (int i = 0; i < tile_set_scenes_collection_source->get_scene_tiles_count(); i++) { + int scene_id = tile_set_scenes_collection_source->get_scene_tile_id(i); + + Ref<PackedScene> scene = tile_set_scenes_collection_source->get_scene_tile_scene(scene_id); + + int item_index = 0; + if (scene.is_valid()) { + item_index = scene_tiles_list->add_item(vformat("%s (path:%s id:%d)", scene->get_path().get_file().get_basename(), scene->get_path(), scene_id)); + Variant udata = i; + EditorResourcePreview::get_singleton()->queue_edited_resource_preview(scene, this, "_scene_thumbnail_done", udata); + } else { + item_index = scene_tiles_list->add_item(TTR("Tile with Invalid Scene"), get_theme_icon("PackedScene", "EditorIcons")); + } + scene_tiles_list->set_item_metadata(item_index, scene_id); + + if (old_selected_scene_id >= 0 && scene_id == old_selected_scene_id) { + to_reselect = i; + } + } + + // Reselect if needed. + if (to_reselect >= 0) { + scene_tiles_list->select(to_reselect); + } + + // Icon size update. + int int_size = int(EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size")) * EDSCALE; + scene_tiles_list->set_fixed_icon_size(Vector2(int_size, int_size)); +} + +void TileSetScenesCollectionSourceEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: + scene_tile_add_button->set_icon(get_theme_icon("Add", "EditorIcons")); + scene_tile_delete_button->set_icon(get_theme_icon("Remove", "EditorIcons")); + _update_scenes_list(); + break; + case NOTIFICATION_INTERNAL_PROCESS: + if (tile_set_scenes_collection_source_changed_needs_update) { + // Update everything. + _update_source_inspector(); + _update_scenes_list(); + _update_action_buttons(); + _update_tile_inspector(); + tile_set_scenes_collection_source_changed_needs_update = false; + } + break; + case NOTIFICATION_VISIBILITY_CHANGED: + // Update things just in case. + _update_scenes_list(); + _update_action_buttons(); + break; + default: + break; + } +} + +void TileSetScenesCollectionSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id) { + ERR_FAIL_COND(!p_tile_set.is_valid()); + ERR_FAIL_COND(!p_tile_set_scenes_collection_source); + ERR_FAIL_COND(p_source_id < 0); + ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_scenes_collection_source); + + if (p_tile_set == tile_set && p_tile_set_scenes_collection_source == tile_set_scenes_collection_source && p_source_id == tile_set_source_id) { + return; + } + + // Remove listener for old objects. + if (tile_set_scenes_collection_source) { + tile_set_scenes_collection_source->disconnect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed)); + } + + // Change the edited object. + tile_set = p_tile_set; + tile_set_scenes_collection_source = p_tile_set_scenes_collection_source; + tile_set_source_id = p_source_id; + + // Add the listener again. + if (tile_set_scenes_collection_source) { + tile_set_scenes_collection_source->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed)); + } + + // Update everything. + _update_source_inspector(); + _update_scenes_list(); + _update_action_buttons(); + _update_tile_inspector(); +} + +void TileSetScenesCollectionSourceEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { + if (!can_drop_data_fw(p_point, p_data, p_from)) { + return; + } + + if (p_from == scene_tiles_list) { + // Handle dropping a texture in the list of atlas resources. + int scene_id = -1; + Dictionary d = p_data; + Vector<String> files = d["files"]; + for (int i = 0; i < files.size(); i++) { + Ref<PackedScene> resource = ResourceLoader::load(files[i]); + if (resource.is_valid()) { + scene_id = tile_set_scenes_collection_source->get_next_scene_tile_id(); + undo_redo->create_action(TTR("Add a Scene Tile")); + undo_redo->add_do_method(tile_set_scenes_collection_source, "create_scene_tile", resource, scene_id); + undo_redo->add_undo_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id); + undo_redo->commit_action(); + } + } + + _update_scenes_list(); + _update_action_buttons(); + _update_tile_inspector(); + } +} + +bool TileSetScenesCollectionSourceEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { + if (p_from == scene_tiles_list) { + Dictionary d = p_data; + + if (!d.has("type")) { + return false; + } + + // Check if we have a Texture2D. + if (String(d["type"]) == "files") { + Vector<String> files = d["files"]; + + if (files.size() == 0) { + return false; + } + + for (int i = 0; i < files.size(); i++) { + String file = files[i]; + String ftype = EditorFileSystem::get_singleton()->get_file_type(file); + + if (!ClassDB::is_parent_class(ftype, "PackedScene")) { + return false; + } + } + + return true; + } + } + return false; +} + +void TileSetScenesCollectionSourceEditor::_bind_methods() { + ADD_SIGNAL(MethodInfo("source_id_changed", PropertyInfo(Variant::INT, "source_id"))); + + ClassDB::bind_method(D_METHOD("_scene_thumbnail_done"), &TileSetScenesCollectionSourceEditor::_scene_thumbnail_done); + ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &TileSetScenesCollectionSourceEditor::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("drop_data_fw"), &TileSetScenesCollectionSourceEditor::drop_data_fw); +} + +TileSetScenesCollectionSourceEditor::TileSetScenesCollectionSourceEditor() { + // -- Right side -- + HSplitContainer *split_container_right_side = memnew(HSplitContainer); + split_container_right_side->set_h_size_flags(SIZE_EXPAND_FILL); + add_child(split_container_right_side); + + // Middle panel. + ScrollContainer *middle_panel = memnew(ScrollContainer); + middle_panel->set_enable_h_scroll(false); + middle_panel->set_custom_minimum_size(Size2i(200, 0) * EDSCALE); + split_container_right_side->add_child(middle_panel); + + VBoxContainer *middle_vbox_container = memnew(VBoxContainer); + middle_vbox_container->set_h_size_flags(SIZE_EXPAND_FILL); + middle_panel->add_child(middle_vbox_container); + + // Scenes collection source inspector. + scenes_collection_source_inspector_label = memnew(Label); + scenes_collection_source_inspector_label->set_text(TTR("Scenes collection properties:")); + middle_vbox_container->add_child(scenes_collection_source_inspector_label); + + scenes_collection_source_proxy_object = memnew(TileSetScenesCollectionProxyObject()); + scenes_collection_source_proxy_object->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_scenes_collection_source_proxy_object_changed)); + + scenes_collection_source_inspector = memnew(EditorInspector); + scenes_collection_source_inspector->set_undo_redo(undo_redo); + scenes_collection_source_inspector->set_enable_v_scroll(false); + scenes_collection_source_inspector->edit(scenes_collection_source_proxy_object); + middle_vbox_container->add_child(scenes_collection_source_inspector); + + // Tile inspector. + tile_inspector_label = memnew(Label); + tile_inspector_label->set_text(TTR("Tile properties:")); + tile_inspector_label->hide(); + middle_vbox_container->add_child(tile_inspector_label); + + tile_proxy_object = memnew(SceneTileProxyObject(this)); + tile_proxy_object->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_scenes_list).unbind(1)); + tile_proxy_object->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_action_buttons).unbind(1)); + + tile_inspector = memnew(EditorInspector); + tile_inspector->set_undo_redo(undo_redo); + tile_inspector->set_enable_v_scroll(false); + tile_inspector->edit(tile_proxy_object); + tile_inspector->set_use_folding(true); + middle_vbox_container->add_child(tile_inspector); + + // Scenes list. + VBoxContainer *right_vbox_container = memnew(VBoxContainer); + split_container_right_side->add_child(right_vbox_container); + + scene_tiles_list = memnew(ItemList); + scene_tiles_list->set_h_size_flags(SIZE_EXPAND_FILL); + scene_tiles_list->set_v_size_flags(SIZE_EXPAND_FILL); + scene_tiles_list->set_drag_forwarding(this); + scene_tiles_list->connect("item_selected", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_tile_inspector).unbind(1)); + scene_tiles_list->connect("item_selected", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_action_buttons).unbind(1)); + scene_tiles_list->connect("item_activated", callable_mp(this, &TileSetScenesCollectionSourceEditor::_scenes_list_item_activated)); + scene_tiles_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); + right_vbox_container->add_child(scene_tiles_list); + + HBoxContainer *scenes_bottom_actions = memnew(HBoxContainer); + right_vbox_container->add_child(scenes_bottom_actions); + + scene_tile_add_button = memnew(Button); + scene_tile_add_button->set_flat(true); + scene_tile_add_button->connect("pressed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_source_add_pressed)); + scenes_bottom_actions->add_child(scene_tile_add_button); + + scene_tile_delete_button = memnew(Button); + scene_tile_delete_button->set_flat(true); + scene_tile_delete_button->set_disabled(true); + scene_tile_delete_button->connect("pressed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_source_delete_pressed)); + scenes_bottom_actions->add_child(scene_tile_delete_button); +} + +TileSetScenesCollectionSourceEditor::~TileSetScenesCollectionSourceEditor() { + memdelete(scenes_collection_source_proxy_object); + memdelete(tile_proxy_object); +} diff --git a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h new file mode 100644 index 0000000000..195aa79bc4 --- /dev/null +++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h @@ -0,0 +1,139 @@ +/*************************************************************************/ +/* tile_set_scenes_collection_source_editor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TILE_SET_SCENES_COLLECTION_SOURCE_EDITOR_H +#define TILE_SET_SCENES_COLLECTION_SOURCE_EDITOR_H + +#include "editor/editor_node.h" +#include "scene/gui/box_container.h" +#include "scene/resources/tile_set.h" + +class TileSetScenesCollectionSourceEditor : public HBoxContainer { + GDCLASS(TileSetScenesCollectionSourceEditor, HBoxContainer); + +private: + // -- Proxy object for an atlas source, needed by the inspector -- + class TileSetScenesCollectionProxyObject : public Object { + GDCLASS(TileSetScenesCollectionProxyObject, Object); + + private: + Ref<TileSet> tile_set; + TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr; + int source_id = -1; + + protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + static void _bind_methods(); + + public: + void set_id(int p_id); + int get_id(); + + void edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id); + }; + + // -- Proxy object for a tile, needed by the inspector -- + class SceneTileProxyObject : public Object { + GDCLASS(SceneTileProxyObject, Object); + + private: + TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor; + + TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr; + int source_id; + int scene_id; + + protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + + static void _bind_methods(); + + public: + // Update the proxyed object. + void edit(TileSetScenesCollectionSource *p_tile_set_atlas_source, int p_scene_id); + + SceneTileProxyObject(TileSetScenesCollectionSourceEditor *p_tiles_set_scenes_collection_source_editor) { + tile_set_scenes_collection_source_editor = p_tiles_set_scenes_collection_source_editor; + } + }; + +private: + Ref<TileSet> tile_set; + TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr; + int tile_set_source_id = -1; + + UndoRedo *undo_redo = EditorNode::get_undo_redo(); + + bool tile_set_scenes_collection_source_changed_needs_update = false; + + // Source inspector. + TileSetScenesCollectionProxyObject *scenes_collection_source_proxy_object; + Label *scenes_collection_source_inspector_label; + EditorInspector *scenes_collection_source_inspector; + + // Tile inspector. + SceneTileProxyObject *tile_proxy_object; + Label *tile_inspector_label; + EditorInspector *tile_inspector; + + ItemList *scene_tiles_list; + Button *scene_tile_add_button; + Button *scene_tile_delete_button; + + void _tile_set_scenes_collection_source_changed(); + void _scenes_collection_source_proxy_object_changed(String p_what); + void _scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud); + void _scenes_list_item_activated(int p_index); + + void _source_add_pressed(); + void _source_delete_pressed(); + + // Update methods. + void _update_source_inspector(); + void _update_tile_inspector(); + void _update_scenes_list(); + void _update_action_buttons(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id); + void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; + TileSetScenesCollectionSourceEditor(); + ~TileSetScenesCollectionSourceEditor(); +}; + +#endif diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 1ffe4853a0..2d0ff24723 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -100,7 +100,6 @@ private: FileDialog *fdialog_install; String zip_path; String zip_title; - String zip_root; AcceptDialog *dialog_error; String fav_dir; @@ -201,9 +200,7 @@ private: char fname[16384]; ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0); - String fname_str = String(fname); - if (fname_str.ends_with("project.godot")) { - zip_root = fname_str.substr(0, fname_str.rfind("project.godot")); + if (String(fname).ends_with("project.godot")) { break; } @@ -524,7 +521,24 @@ private: return; } + // Find the zip_root + String zip_root; int ret = unzGoToFirstFile(pkg); + while (ret == UNZ_OK) { + unz_file_info info; + char fname[16384]; + unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0); + + String name = fname; + if (name.ends_with("project.godot")) { + zip_root = name.substr(0, name.rfind("project.godot")); + break; + } + + ret = unzGoToNextFile(pkg); + } + + ret = unzGoToFirstFile(pkg); Vector<String> failed_files; diff --git a/modules/opensimplex/doc_classes/NoiseTexture.xml b/modules/opensimplex/doc_classes/NoiseTexture.xml index 38c5138482..a12412a9cd 100644 --- a/modules/opensimplex/doc_classes/NoiseTexture.xml +++ b/modules/opensimplex/doc_classes/NoiseTexture.xml @@ -31,6 +31,9 @@ <member name="noise" type="OpenSimplexNoise" setter="set_noise" getter="get_noise"> The [OpenSimplexNoise] instance used to generate the noise. </member> + <member name="noise_offset" type="Vector2" setter="set_noise_offset" getter="get_noise_offset" default="Vector2( 0, 0 )"> + An offset used to specify the noise space coordinate of the top left corner of the generated noise. This value is ignored if [member seamless] is enabled. + </member> <member name="seamless" type="bool" setter="set_seamless" getter="get_seamless" default="false"> Whether the texture can be tiled without visible seams or not. Seamless textures take longer to generate. [b]Note:[/b] Seamless noise has a lower contrast compared to non-seamless noise. This is due to the way noise uses higher dimensions for generating seamless noise. diff --git a/modules/opensimplex/doc_classes/OpenSimplexNoise.xml b/modules/opensimplex/doc_classes/OpenSimplexNoise.xml index ad82f87213..2fdbd61ee7 100644 --- a/modules/opensimplex/doc_classes/OpenSimplexNoise.xml +++ b/modules/opensimplex/doc_classes/OpenSimplexNoise.xml @@ -31,8 +31,10 @@ </argument> <argument index="1" name="height" type="int"> </argument> + <argument index="2" name="noise_offset" type="Vector2" default="Vector2( 0, 0 )"> + </argument> <description> - Generate a noise image in [constant Image.FORMAT_L8] format with the requested [code]width[/code] and [code]height[/code], based on the current noise parameters. + Generate a noise image in [constant Image.FORMAT_L8] format with the requested [code]width[/code] and [code]height[/code], based on the current noise parameters. If [code]noise_offset[/code] is specified, then the offset value is used as the coordinates of the top-left corner of the generated noise. </description> </method> <method name="get_noise_1d" qualifiers="const"> diff --git a/modules/opensimplex/noise_texture.cpp b/modules/opensimplex/noise_texture.cpp index 7272d32fac..9e0155da94 100644 --- a/modules/opensimplex/noise_texture.cpp +++ b/modules/opensimplex/noise_texture.cpp @@ -52,6 +52,9 @@ void NoiseTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("set_noise", "noise"), &NoiseTexture::set_noise); ClassDB::bind_method(D_METHOD("get_noise"), &NoiseTexture::get_noise); + ClassDB::bind_method(D_METHOD("set_noise_offset", "noise_offset"), &NoiseTexture::set_noise_offset); + ClassDB::bind_method(D_METHOD("get_noise_offset"), &NoiseTexture::get_noise_offset); + ClassDB::bind_method(D_METHOD("set_seamless", "seamless"), &NoiseTexture::set_seamless); ClassDB::bind_method(D_METHOD("get_seamless"), &NoiseTexture::get_seamless); @@ -71,6 +74,7 @@ void NoiseTexture::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "as_normal_map"), "set_as_normal_map", "is_normal_map"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bump_strength", PROPERTY_HINT_RANGE, "0,32,0.1,or_greater"), "set_bump_strength", "get_bump_strength"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "noise", PROPERTY_HINT_RESOURCE_TYPE, "OpenSimplexNoise"), "set_noise", "get_noise"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "noise_offset"), "set_noise_offset", "get_noise_offset"); } void NoiseTexture::_validate_property(PropertyInfo &property) const { @@ -130,7 +134,7 @@ Ref<Image> NoiseTexture::_generate_texture() { if (seamless) { image = ref_noise->get_seamless_image(size.x); } else { - image = ref_noise->get_image(size.x, size.y); + image = ref_noise->get_image(size.x, size.y, noise_offset); } if (as_normal_map) { @@ -198,6 +202,14 @@ void NoiseTexture::set_height(int p_height) { _queue_update(); } +void NoiseTexture::set_noise_offset(Vector2 p_noise_offset) { + if (noise_offset == p_noise_offset) { + return; + } + noise_offset = p_noise_offset; + _queue_update(); +} + void NoiseTexture::set_seamless(bool p_seamless) { if (p_seamless == seamless) { return; @@ -245,6 +257,10 @@ int NoiseTexture::get_height() const { return size.y; } +Vector2 NoiseTexture::get_noise_offset() const { + return noise_offset; +} + RID NoiseTexture::get_rid() const { if (!texture.is_valid()) { texture = RS::get_singleton()->texture_2d_placeholder_create(); diff --git a/modules/opensimplex/noise_texture.h b/modules/opensimplex/noise_texture.h index 6983ae18fe..20a53bb58b 100644 --- a/modules/opensimplex/noise_texture.h +++ b/modules/opensimplex/noise_texture.h @@ -56,6 +56,7 @@ private: Ref<OpenSimplexNoise> noise; Vector2i size = Vector2i(512, 512); + Vector2 noise_offset; bool seamless = false; bool as_normal_map = false; float bump_strength = 8.0; @@ -79,6 +80,9 @@ public: void set_width(int p_width); void set_height(int p_height); + void set_noise_offset(Vector2 p_noise_offset); + Vector2 get_noise_offset() const; + void set_seamless(bool p_seamless); bool get_seamless(); diff --git a/modules/opensimplex/open_simplex_noise.cpp b/modules/opensimplex/open_simplex_noise.cpp index 3773946112..f0a8867284 100644 --- a/modules/opensimplex/open_simplex_noise.cpp +++ b/modules/opensimplex/open_simplex_noise.cpp @@ -96,7 +96,7 @@ void OpenSimplexNoise::set_lacunarity(float p_lacunarity) { emit_changed(); } -Ref<Image> OpenSimplexNoise::get_image(int p_width, int p_height) const { +Ref<Image> OpenSimplexNoise::get_image(int p_width, int p_height, const Vector2 &p_noise_offset) const { Vector<uint8_t> data; data.resize(p_width * p_height); @@ -104,7 +104,7 @@ Ref<Image> OpenSimplexNoise::get_image(int p_width, int p_height) const { for (int i = 0; i < p_height; i++) { for (int j = 0; j < p_width; j++) { - float v = get_noise_2d(j, i); + float v = get_noise_2d(float(j) + p_noise_offset.x, float(i) + p_noise_offset.y); v = v * 0.5 + 0.5; // Normalize [0..1] wd8[(i * p_width + j)] = uint8_t(CLAMP(v * 255.0, 0, 255)); } @@ -161,7 +161,7 @@ void OpenSimplexNoise::_bind_methods() { ClassDB::bind_method(D_METHOD("set_lacunarity", "lacunarity"), &OpenSimplexNoise::set_lacunarity); ClassDB::bind_method(D_METHOD("get_lacunarity"), &OpenSimplexNoise::get_lacunarity); - ClassDB::bind_method(D_METHOD("get_image", "width", "height"), &OpenSimplexNoise::get_image); + ClassDB::bind_method(D_METHOD("get_image", "width", "height", "noise_offset"), &OpenSimplexNoise::get_image, DEFVAL(Vector2())); ClassDB::bind_method(D_METHOD("get_seamless_image", "size"), &OpenSimplexNoise::get_seamless_image); ClassDB::bind_method(D_METHOD("get_noise_1d", "x"), &OpenSimplexNoise::get_noise_1d); diff --git a/modules/opensimplex/open_simplex_noise.h b/modules/opensimplex/open_simplex_noise.h index 847c157409..bb50c523d2 100644 --- a/modules/opensimplex/open_simplex_noise.h +++ b/modules/opensimplex/open_simplex_noise.h @@ -75,7 +75,7 @@ public: void set_lacunarity(float p_lacunarity); float get_lacunarity() const { return lacunarity; } - Ref<Image> get_image(int p_width, int p_height) const; + Ref<Image> get_image(int p_width, int p_height, const Vector2 &p_noise_offset = Vector2()) const; Ref<Image> get_seamless_image(int p_size) const; float get_noise_1d(float x) const; diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index 24be07d8ec..d68ab7f7c9 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -6,7 +6,7 @@ javascript_files = [ "audio_driver_javascript.cpp", "display_server_javascript.cpp", "http_client_javascript.cpp", - "javascript_eval.cpp", + "javascript_singleton.cpp", "javascript_main.cpp", "os_javascript.cpp", "api/javascript_tools_editor_plugin.cpp", @@ -26,7 +26,7 @@ sys_env.AddJSLibraries( if env["tools"]: sys_env.AddJSLibraries(["js/libs/library_godot_editor_tools.js"]) if env["javascript_eval"]: - sys_env.AddJSLibraries(["js/libs/library_godot_eval.js"]) + sys_env.AddJSLibraries(["js/libs/library_godot_javascript_singleton.js"]) for lib in sys_env["JS_LIBS"]: sys_env.Append(LINKFLAGS=["--js-library", lib]) diff --git a/platform/javascript/api/api.cpp b/platform/javascript/api/api.cpp index 2f7bde065f..039ce815e4 100644 --- a/platform/javascript/api/api.cpp +++ b/platform/javascript/api/api.cpp @@ -30,13 +30,14 @@ #include "api.h" #include "core/config/engine.h" -#include "javascript_eval.h" +#include "javascript_singleton.h" #include "javascript_tools_editor_plugin.h" static JavaScript *javascript_eval; void register_javascript_api() { JavaScriptToolsEditorPlugin::initialize(); + ClassDB::register_virtual_class<JavaScriptObject>(); ClassDB::register_virtual_class<JavaScript>(); javascript_eval = memnew(JavaScript); Engine::get_singleton()->add_singleton(Engine::Singleton("JavaScript", javascript_eval)); @@ -61,10 +62,41 @@ JavaScript::~JavaScript() {} void JavaScript::_bind_methods() { ClassDB::bind_method(D_METHOD("eval", "code", "use_global_execution_context"), &JavaScript::eval, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_interface", "interface"), &JavaScript::get_interface); + ClassDB::bind_method(D_METHOD("create_callback", "callable"), &JavaScript::create_callback); + { + MethodInfo mi; + mi.name = "create_object"; + mi.arguments.push_back(PropertyInfo(Variant::STRING, "object")); + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "create_object", &JavaScript::_create_object_bind, mi); + } } #if !defined(JAVASCRIPT_ENABLED) || !defined(JAVASCRIPT_EVAL_ENABLED) Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { return Variant(); } + +Ref<JavaScriptObject> JavaScript::get_interface(const String &p_interface) { + return Ref<JavaScriptObject>(); +} + +Ref<JavaScriptObject> JavaScript::create_callback(const Callable &p_callable) { + return Ref<JavaScriptObject>(); +} + +Variant JavaScript::_create_object_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + if (p_argcount < 1) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 0; + return Ref<JavaScriptObject>(); + } + if (p_args[0]->get_type() != Variant::STRING) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + return Ref<JavaScriptObject>(); + } + return Ref<JavaScriptObject>(); +} #endif diff --git a/platform/javascript/api/javascript_eval.h b/platform/javascript/api/javascript_singleton.h index 24f7648ed9..45e9950acb 100644 --- a/platform/javascript/api/javascript_eval.h +++ b/platform/javascript/api/javascript_singleton.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* javascript_eval.h */ +/* javascript_singleton.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,10 +28,21 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef JAVASCRIPT_EVAL_H -#define JAVASCRIPT_EVAL_H +#ifndef JAVASCRIPT_SINGLETON_H +#define JAVASCRIPT_SINGLETON_H #include "core/object/class_db.h" +#include "core/object/reference.h" + +class JavaScriptObject : public Reference { +private: + GDCLASS(JavaScriptObject, Reference); + +protected: + virtual bool _set(const StringName &p_name, const Variant &p_value) { return false; } + virtual bool _get(const StringName &p_name, Variant &r_ret) const { return false; } + virtual void _get_property_list(List<PropertyInfo> *p_list) const {} +}; class JavaScript : public Object { private: @@ -44,10 +55,13 @@ protected: public: Variant eval(const String &p_code, bool p_use_global_exec_context = false); + Ref<JavaScriptObject> get_interface(const String &p_interface); + Ref<JavaScriptObject> create_callback(const Callable &p_callable); + Variant _create_object_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); static JavaScript *get_singleton(); JavaScript(); ~JavaScript(); }; -#endif // JAVASCRIPT_EVAL_H +#endif // JAVASCRIPT_SINGLETON_H diff --git a/platform/javascript/javascript_eval.cpp b/platform/javascript/javascript_eval.cpp deleted file mode 100644 index cb19dd20d4..0000000000 --- a/platform/javascript/javascript_eval.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/*************************************************************************/ -/* javascript_eval.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -#ifdef JAVASCRIPT_EVAL_ENABLED - -#include "api/javascript_eval.h" -#include "emscripten.h" - -extern "C" { -union js_eval_ret { - uint32_t b; - double d; - char *s; -}; - -extern int godot_js_eval(const char *p_js, int p_use_global_ctx, union js_eval_ret *p_union_ptr, void *p_byte_arr, void *p_byte_arr_write, void *(*p_callback)(void *p_ptr, void *p_ptr2, int p_len)); -} - -void *resize_PackedByteArray_and_open_write(void *p_arr, void *r_write, int p_len) { - PackedByteArray *arr = (PackedByteArray *)p_arr; - VectorWriteProxy<uint8_t> *write = (VectorWriteProxy<uint8_t> *)r_write; - arr->resize(p_len); - *write = arr->write; - return arr->ptrw(); -} - -Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { - union js_eval_ret js_data; - PackedByteArray arr; - VectorWriteProxy<uint8_t> arr_write; - - Variant::Type return_type = static_cast<Variant::Type>(godot_js_eval(p_code.utf8().get_data(), p_use_global_exec_context, &js_data, &arr, &arr_write, resize_PackedByteArray_and_open_write)); - - switch (return_type) { - case Variant::BOOL: - return js_data.b; - case Variant::FLOAT: - return js_data.d; - case Variant::STRING: { - String str = String::utf8(js_data.s); - free(js_data.s); // Must free the string allocated in JS. - return str; - } - case Variant::PACKED_BYTE_ARRAY: - arr_write = VectorWriteProxy<uint8_t>(); - return arr; - default: - return Variant(); - } -} - -#endif // JAVASCRIPT_EVAL_ENABLED diff --git a/platform/javascript/javascript_singleton.cpp b/platform/javascript/javascript_singleton.cpp new file mode 100644 index 0000000000..67908a18da --- /dev/null +++ b/platform/javascript/javascript_singleton.cpp @@ -0,0 +1,338 @@ +/*************************************************************************/ +/* javascript_singleton.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifdef JAVASCRIPT_EVAL_ENABLED + +#include "api/javascript_singleton.h" +#include "emscripten.h" + +extern "C" { +typedef union { + int64_t i; + double r; + void *p; +} godot_js_wrapper_ex; + +typedef int (*GodotJSWrapperVariant2JSCallback)(const void **p_args, int p_pos, godot_js_wrapper_ex *r_val, void **p_lock); +typedef void (*GodotJSWrapperFreeLockCallback)(void **p_lock, int p_type); +extern int godot_js_wrapper_interface_get(const char *p_name); +extern int godot_js_wrapper_object_call(int p_id, const char *p_method, void **p_args, int p_argc, GodotJSWrapperVariant2JSCallback p_variant2js_callback, godot_js_wrapper_ex *p_cb_rval, void **p_lock, GodotJSWrapperFreeLockCallback p_lock_callback); +extern int godot_js_wrapper_object_get(int p_id, godot_js_wrapper_ex *p_val, const char *p_prop); +extern int godot_js_wrapper_object_getvar(int p_id, int p_type, godot_js_wrapper_ex *p_val); +extern int godot_js_wrapper_object_setvar(int p_id, int p_key_type, godot_js_wrapper_ex *p_key_ex, int p_val_type, godot_js_wrapper_ex *p_val_ex); +extern void godot_js_wrapper_object_set(int p_id, const char *p_name, int p_type, godot_js_wrapper_ex *p_val); +extern void godot_js_wrapper_object_unref(int p_id); +extern int godot_js_wrapper_create_cb(void *p_ref, void (*p_callback)(void *p_ref, int p_arg_id, int p_argc)); +extern int godot_js_wrapper_create_object(const char *p_method, void **p_args, int p_argc, GodotJSWrapperVariant2JSCallback p_variant2js_callback, godot_js_wrapper_ex *p_cb_rval, void **p_lock, GodotJSWrapperFreeLockCallback p_lock_callback); +}; + +class JavaScriptObjectImpl : public JavaScriptObject { +private: + friend class JavaScript; + + int _js_id = 0; + Callable _callable; + + static int _variant2js(const void **p_args, int p_pos, godot_js_wrapper_ex *r_val, void **p_lock); + static void _free_lock(void **p_lock, int p_type); + static Variant _js2variant(int p_type, godot_js_wrapper_ex *p_val); + static void *_alloc_variants(int p_size); + static void _callback(void *p_ref, int p_arg_id, int p_argc); + +protected: + bool _set(const StringName &p_name, const Variant &p_value) override; + bool _get(const StringName &p_name, Variant &r_ret) const override; + void _get_property_list(List<PropertyInfo> *p_list) const override; + +public: + Variant getvar(const Variant &p_key, bool *r_valid = nullptr) const override; + void setvar(const Variant &p_key, const Variant &p_value, bool *r_valid = nullptr) override; + Variant call(const StringName &p_method, const Variant **p_args, int p_argc, Callable::CallError &r_error) override; + JavaScriptObjectImpl() {} + JavaScriptObjectImpl(int p_id) { _js_id = p_id; } + ~JavaScriptObjectImpl() { + if (_js_id) { + godot_js_wrapper_object_unref(_js_id); + } + } +}; + +bool JavaScriptObjectImpl::_set(const StringName &p_name, const Variant &p_value) { + ERR_FAIL_COND_V_MSG(!_js_id, false, "Invalid JS instance"); + const String name = p_name; + godot_js_wrapper_ex exchange; + void *lock = nullptr; + const Variant *v = &p_value; + int type = _variant2js((const void **)&v, 0, &exchange, &lock); + godot_js_wrapper_object_set(_js_id, name.utf8().get_data(), type, &exchange); + if (lock) { + _free_lock(&lock, type); + } + return true; +} + +bool JavaScriptObjectImpl::_get(const StringName &p_name, Variant &r_ret) const { + ERR_FAIL_COND_V_MSG(!_js_id, false, "Invalid JS instance"); + const String name = p_name; + godot_js_wrapper_ex exchange; + int type = godot_js_wrapper_object_get(_js_id, &exchange, name.utf8().get_data()); + r_ret = _js2variant(type, &exchange); + return true; +} + +Variant JavaScriptObjectImpl::getvar(const Variant &p_key, bool *r_valid) const { + if (r_valid) { + *r_valid = false; + } + godot_js_wrapper_ex exchange; + void *lock = nullptr; + const Variant *v = &p_key; + int prop_type = _variant2js((const void **)&v, 0, &exchange, &lock); + int type = godot_js_wrapper_object_getvar(_js_id, prop_type, &exchange); + if (lock) { + _free_lock(&lock, prop_type); + } + if (type < 0) { + return Variant(); + } + if (r_valid) { + *r_valid = true; + } + return _js2variant(type, &exchange); +} + +void JavaScriptObjectImpl::setvar(const Variant &p_key, const Variant &p_value, bool *r_valid) { + if (r_valid) { + *r_valid = false; + } + godot_js_wrapper_ex kex, vex; + void *klock = nullptr; + void *vlock = nullptr; + const Variant *kv = &p_key; + const Variant *vv = &p_value; + int ktype = _variant2js((const void **)&kv, 0, &kex, &klock); + int vtype = _variant2js((const void **)&vv, 0, &vex, &vlock); + int ret = godot_js_wrapper_object_setvar(_js_id, ktype, &kex, vtype, &vex); + if (klock) { + _free_lock(&klock, ktype); + } + if (vlock) { + _free_lock(&vlock, vtype); + } + if (ret == 0 && r_valid) { + *r_valid = true; + } +} + +void JavaScriptObjectImpl::_get_property_list(List<PropertyInfo> *p_list) const { +} + +void JavaScriptObjectImpl::_free_lock(void **p_lock, int p_type) { + ERR_FAIL_COND_MSG(*p_lock == nullptr, "No lock to free!"); + const Variant::Type type = (Variant::Type)p_type; + switch (type) { + case Variant::STRING: { + CharString *cs = (CharString *)(*p_lock); + memdelete(cs); + *p_lock = nullptr; + } break; + default: + ERR_FAIL_MSG("Unknown lock type to free. Likely a bug."); + } +} + +Variant JavaScriptObjectImpl::_js2variant(int p_type, godot_js_wrapper_ex *p_val) { + Variant::Type type = (Variant::Type)p_type; + switch (type) { + case Variant::BOOL: + return Variant((bool)p_val->i); + case Variant::INT: + return p_val->i; + case Variant::FLOAT: + return p_val->r; + case Variant::STRING: { + String out((const char *)p_val->p); + free(p_val->p); + return out; + } + case Variant::OBJECT: { + return memnew(JavaScriptObjectImpl(p_val->i)); + } + default: + return Variant(); + } +} + +int JavaScriptObjectImpl::_variant2js(const void **p_args, int p_pos, godot_js_wrapper_ex *r_val, void **p_lock) { + const Variant **args = (const Variant **)p_args; + const Variant *v = args[p_pos]; + Variant::Type type = v->get_type(); + switch (type) { + case Variant::BOOL: + r_val->i = v->operator bool() ? 1 : 0; + break; + case Variant::INT: { + const int64_t tmp = v->operator int64_t(); + if (tmp >= 1 << 31) { + r_val->r = (double)tmp; + return Variant::FLOAT; + } + r_val->i = v->operator int64_t(); + } break; + case Variant::FLOAT: + r_val->r = v->operator real_t(); + break; + case Variant::STRING: { + CharString *cs = memnew(CharString(v->operator String().utf8())); + r_val->p = (void *)cs->get_data(); + *p_lock = (void *)cs; + } break; + case Variant::OBJECT: { + JavaScriptObject *js_obj = Object::cast_to<JavaScriptObject>(v->operator Object *()); + r_val->i = js_obj != nullptr ? ((JavaScriptObjectImpl *)js_obj)->_js_id : 0; + } break; + default: + break; + } + return type; +} + +Variant JavaScriptObjectImpl::call(const StringName &p_method, const Variant **p_args, int p_argc, Callable::CallError &r_error) { + godot_js_wrapper_ex exchange; + const String method = p_method; + void *lock = nullptr; + const int type = godot_js_wrapper_object_call(_js_id, method.utf8().get_data(), (void **)p_args, p_argc, &_variant2js, &exchange, &lock, &_free_lock); + r_error.error = Callable::CallError::CALL_OK; + if (type < 0) { + r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return Variant(); + } + return _js2variant(type, &exchange); +} + +void JavaScriptObjectImpl::_callback(void *p_ref, int p_args_id, int p_argc) { + const JavaScriptObjectImpl *obj = (JavaScriptObjectImpl *)p_ref; + ERR_FAIL_COND_MSG(obj->_callable.is_null(), "JavaScript callback failed."); + Vector<const Variant *> argp; + Array arg_arr; + for (int i = 0; i < p_argc; i++) { + godot_js_wrapper_ex exchange; + exchange.i = i; + int type = godot_js_wrapper_object_getvar(p_args_id, Variant::INT, &exchange); + arg_arr.push_back(_js2variant(type, &exchange)); + } + Variant arg = arg_arr; + const Variant *argv[1] = { &arg }; + Callable::CallError err; + Variant ret; + obj->_callable.call(argv, 1, ret, err); +} + +Ref<JavaScriptObject> JavaScript::create_callback(const Callable &p_callable) { + Ref<JavaScriptObjectImpl> out = memnew(JavaScriptObjectImpl); + out->_callable = p_callable; + out->_js_id = godot_js_wrapper_create_cb(out.ptr(), JavaScriptObjectImpl::_callback); + return out; +} + +Ref<JavaScriptObject> JavaScript::get_interface(const String &p_interface) { + int js_id = godot_js_wrapper_interface_get(p_interface.utf8().get_data()); + ERR_FAIL_COND_V_MSG(!js_id, Ref<JavaScriptObject>(), "No interface '" + p_interface + "' registered."); + return Ref<JavaScriptObject>(memnew(JavaScriptObjectImpl(js_id))); +} + +Variant JavaScript::_create_object_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + if (p_argcount < 1) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 0; + return Ref<JavaScriptObject>(); + } + if (p_args[0]->get_type() != Variant::STRING) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + return Ref<JavaScriptObject>(); + } + godot_js_wrapper_ex exchange; + const String object = *p_args[0]; + void *lock = nullptr; + const Variant **args = p_argcount > 1 ? &p_args[1] : nullptr; + const int type = godot_js_wrapper_create_object(object.utf8().get_data(), (void **)args, p_argcount - 1, &JavaScriptObjectImpl::_variant2js, &exchange, &lock, &JavaScriptObjectImpl::_free_lock); + r_error.error = Callable::CallError::CALL_OK; + if (type < 0) { + r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return Ref<JavaScriptObject>(); + } + return JavaScriptObjectImpl::_js2variant(type, &exchange); +} + +extern "C" { +union js_eval_ret { + uint32_t b; + double d; + char *s; +}; + +extern int godot_js_eval(const char *p_js, int p_use_global_ctx, union js_eval_ret *p_union_ptr, void *p_byte_arr, void *p_byte_arr_write, void *(*p_callback)(void *p_ptr, void *p_ptr2, int p_len)); +} + +void *resize_PackedByteArray_and_open_write(void *p_arr, void *r_write, int p_len) { + PackedByteArray *arr = (PackedByteArray *)p_arr; + VectorWriteProxy<uint8_t> *write = (VectorWriteProxy<uint8_t> *)r_write; + arr->resize(p_len); + *write = arr->write; + return arr->ptrw(); +} + +Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { + union js_eval_ret js_data; + PackedByteArray arr; + VectorWriteProxy<uint8_t> arr_write; + + Variant::Type return_type = static_cast<Variant::Type>(godot_js_eval(p_code.utf8().get_data(), p_use_global_exec_context, &js_data, &arr, &arr_write, resize_PackedByteArray_and_open_write)); + + switch (return_type) { + case Variant::BOOL: + return js_data.b; + case Variant::FLOAT: + return js_data.d; + case Variant::STRING: { + String str = String::utf8(js_data.s); + free(js_data.s); // Must free the string allocated in JS. + return str; + } + case Variant::PACKED_BYTE_ARRAY: + arr_write = VectorWriteProxy<uint8_t>(); + return arr; + default: + return Variant(); + } +} +#endif // JAVASCRIPT_EVAL_ENABLED diff --git a/platform/javascript/js/libs/library_godot_eval.js b/platform/javascript/js/libs/library_godot_eval.js deleted file mode 100644 index 9ab392b813..0000000000 --- a/platform/javascript/js/libs/library_godot_eval.js +++ /dev/null @@ -1,86 +0,0 @@ -/*************************************************************************/ -/* library_godot_eval.js */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -const GodotEval = { - godot_js_eval__deps: ['$GodotRuntime'], - godot_js_eval__sig: 'iiiiiii', - godot_js_eval: function (p_js, p_use_global_ctx, p_union_ptr, p_byte_arr, p_byte_arr_write, p_callback) { - const js_code = GodotRuntime.parseString(p_js); - let eval_ret = null; - try { - if (p_use_global_ctx) { - // indirect eval call grants global execution context - const global_eval = eval; // eslint-disable-line no-eval - eval_ret = global_eval(js_code); - } else { - eval_ret = eval(js_code); // eslint-disable-line no-eval - } - } catch (e) { - GodotRuntime.error(e); - } - - switch (typeof eval_ret) { - case 'boolean': - GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'i32'); - return 1; // BOOL - - case 'number': - GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'double'); - return 3; // REAL - - case 'string': - GodotRuntime.setHeapValue(p_union_ptr, GodotRuntime.allocString(eval_ret), '*'); - return 4; // STRING - - case 'object': - if (eval_ret === null) { - break; - } - - if (ArrayBuffer.isView(eval_ret) && !(eval_ret instanceof Uint8Array)) { - eval_ret = new Uint8Array(eval_ret.buffer); - } else if (eval_ret instanceof ArrayBuffer) { - eval_ret = new Uint8Array(eval_ret); - } - if (eval_ret instanceof Uint8Array) { - const func = GodotRuntime.get_func(p_callback); - const bytes_ptr = func(p_byte_arr, p_byte_arr_write, eval_ret.length); - HEAPU8.set(eval_ret, bytes_ptr); - return 20; // POOL_BYTE_ARRAY - } - break; - - // no default - } - return 0; // NIL - }, -}; - -mergeInto(LibraryManager.library, GodotEval); diff --git a/platform/javascript/js/libs/library_godot_javascript_singleton.js b/platform/javascript/js/libs/library_godot_javascript_singleton.js new file mode 100644 index 0000000000..09ef4a1a5d --- /dev/null +++ b/platform/javascript/js/libs/library_godot_javascript_singleton.js @@ -0,0 +1,333 @@ +/*************************************************************************/ +/* library_godot_eval.js */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +const GodotJSWrapper = { + + $GodotJSWrapper__deps: ['$GodotRuntime', '$IDHandler'], + $GodotJSWrapper__postset: 'GodotJSWrapper.proxies = new Map();', + $GodotJSWrapper: { + proxies: null, + + MyProxy: function (val) { + const id = IDHandler.add(this); + GodotJSWrapper.proxies.set(val, id); + let refs = 1; + this.ref = function () { + refs++; + }; + this.unref = function () { + refs--; + if (refs === 0) { + IDHandler.remove(id); + GodotJSWrapper.proxies.delete(val); + } + }; + this.get_val = function () { + return val; + }; + this.get_id = function () { + return id; + }; + }, + + get_proxied: function (val) { + const id = GodotJSWrapper.proxies.get(val); + if (id === undefined) { + const proxy = new GodotJSWrapper.MyProxy(val); + return proxy.get_id(); + } + IDHandler.get(id).ref(); + return id; + }, + + get_proxied_value: function (id) { + const proxy = IDHandler.get(id); + if (proxy === undefined) { + return undefined; + } + return proxy.get_val(); + }, + + variant2js: function (type, val) { + switch (type) { + case 0: + return null; + case 1: + return !!GodotRuntime.getHeapValue(val, 'i64'); + case 2: + return GodotRuntime.getHeapValue(val, 'i64'); + case 3: + return GodotRuntime.getHeapValue(val, 'double'); + case 4: + return GodotRuntime.parseString(GodotRuntime.getHeapValue(val, '*')); + case 21: // OBJECT + return GodotJSWrapper.get_proxied_value(GodotRuntime.getHeapValue(val, 'i64')); + default: + return undefined; + } + }, + + js2variant: function (p_val, p_exchange) { + if (p_val === undefined || p_val === null) { + return 0; // NIL + } + const type = typeof (p_val); + if (type === 'boolean') { + GodotRuntime.setHeapValue(p_exchange, p_val, 'i64'); + return 1; // BOOL + } else if (type === 'number') { + if (Number.isInteger(p_val)) { + GodotRuntime.setHeapValue(p_exchange, p_val, 'i64'); + return 2; // INT + } + GodotRuntime.setHeapValue(p_exchange, p_val, 'double'); + return 3; // REAL + } else if (type === 'string') { + const c_str = GodotRuntime.allocString(p_val); + GodotRuntime.setHeapValue(p_exchange, c_str, '*'); + return 4; // STRING + } + const id = GodotJSWrapper.get_proxied(p_val); + GodotRuntime.setHeapValue(p_exchange, id, 'i64'); + return 21; + }, + }, + + godot_js_wrapper_interface_get__sig: 'ii', + godot_js_wrapper_interface_get: function (p_name) { + const name = GodotRuntime.parseString(p_name); + if (typeof (window[name]) !== 'undefined') { + return GodotJSWrapper.get_proxied(window[name]); + } + return 0; + }, + + godot_js_wrapper_object_get__sig: 'iiii', + godot_js_wrapper_object_get: function (p_id, p_exchange, p_prop) { + const obj = GodotJSWrapper.get_proxied_value(p_id); + if (obj === undefined) { + return 0; + } + if (p_prop) { + const prop = GodotRuntime.parseString(p_prop); + try { + return GodotJSWrapper.js2variant(obj[prop], p_exchange); + } catch (e) { + GodotRuntime.error(`Error getting variable ${prop} on object`, obj); + return 0; // NIL + } + } + return GodotJSWrapper.js2variant(obj, p_exchange); + }, + + godot_js_wrapper_object_set__sig: 'viiii', + godot_js_wrapper_object_set: function (p_id, p_name, p_type, p_exchange) { + const obj = GodotJSWrapper.get_proxied_value(p_id); + if (obj === undefined) { + return; + } + const name = GodotRuntime.parseString(p_name); + try { + obj[name] = GodotJSWrapper.variant2js(p_type, p_exchange); + } catch (e) { + GodotRuntime.error(`Error setting variable ${name} on object`, obj); + } + }, + + godot_js_wrapper_object_call__sig: 'iiiiiiiii', + godot_js_wrapper_object_call: function (p_id, p_method, p_args, p_argc, p_convert_callback, p_exchange, p_lock, p_free_lock_callback) { + const obj = GodotJSWrapper.get_proxied_value(p_id); + if (obj === undefined) { + return -1; + } + const method = GodotRuntime.parseString(p_method); + const convert = GodotRuntime.get_func(p_convert_callback); + const freeLock = GodotRuntime.get_func(p_free_lock_callback); + const args = new Array(p_argc); + for (let i = 0; i < p_argc; i++) { + const type = convert(p_args, i, p_exchange, p_lock); + const lock = GodotRuntime.getHeapValue(p_lock, '*'); + args[i] = GodotJSWrapper.variant2js(type, p_exchange); + if (lock) { + freeLock(p_lock, type); + } + } + try { + const res = obj[method](...args); + return GodotJSWrapper.js2variant(res, p_exchange); + } catch (e) { + GodotRuntime.error(`Error calling method ${method} on:`, obj, 'error:', e); + return -1; + } + }, + + godot_js_wrapper_object_unref__sig: 'vi', + godot_js_wrapper_object_unref: function (p_id) { + const proxy = IDHandler.get(p_id); + if (proxy !== undefined) { + proxy.unref(); + } + }, + + godot_js_wrapper_create_cb__sig: 'vii', + godot_js_wrapper_create_cb: function (p_ref, p_func) { + const func = GodotRuntime.get_func(p_func); + let id = 0; + const cb = function () { + if (!GodotJSWrapper.get_proxied_value(id)) { + return; + } + const args = Array.from(arguments); + func(p_ref, GodotJSWrapper.get_proxied(args), args.length); + }; + id = GodotJSWrapper.get_proxied(cb); + return id; + }, + + godot_js_wrapper_object_getvar__sig: 'iiii', + godot_js_wrapper_object_getvar: function (p_id, p_type, p_exchange) { + const obj = GodotJSWrapper.get_proxied_value(p_id); + if (obj === undefined) { + return -1; + } + const prop = GodotJSWrapper.variant2js(p_type, p_exchange); + if (prop === undefined || prop === null) { + return -1; + } + try { + return GodotJSWrapper.js2variant(obj[prop], p_exchange); + } catch (e) { + GodotRuntime.error(`Error getting variable ${prop} on object`, obj, e); + return -1; + } + }, + + godot_js_wrapper_object_setvar__sig: 'iiiiii', + godot_js_wrapper_object_setvar: function (p_id, p_key_type, p_key_ex, p_val_type, p_val_ex) { + const obj = GodotJSWrapper.get_proxied_value(p_id); + if (obj === undefined) { + return -1; + } + const key = GodotJSWrapper.variant2js(p_key_type, p_key_ex); + try { + obj[key] = GodotJSWrapper.variant2js(p_val_type, p_val_ex); + return 0; + } catch (e) { + GodotRuntime.error(`Error setting variable ${key} on object`, obj); + return -1; + } + }, + + godot_js_wrapper_create_object__sig: 'iiiiiiii', + godot_js_wrapper_create_object: function (p_object, p_args, p_argc, p_convert_callback, p_exchange, p_lock, p_free_lock_callback) { + const name = GodotRuntime.parseString(p_object); + if (typeof (window[name]) === 'undefined') { + return -1; + } + const convert = GodotRuntime.get_func(p_convert_callback); + const freeLock = GodotRuntime.get_func(p_free_lock_callback); + const args = new Array(p_argc); + for (let i = 0; i < p_argc; i++) { + const type = convert(p_args, i, p_exchange, p_lock); + const lock = GodotRuntime.getHeapValue(p_lock, '*'); + args[i] = GodotJSWrapper.variant2js(type, p_exchange); + if (lock) { + freeLock(p_lock, type); + } + } + try { + const res = new window[name](...args); + return GodotJSWrapper.js2variant(res, p_exchange); + } catch (e) { + GodotRuntime.error(`Error calling constructor ${name} with args:`, args, 'error:', e); + return -1; + } + }, +}; + +autoAddDeps(GodotJSWrapper, '$GodotJSWrapper'); +mergeInto(LibraryManager.library, GodotJSWrapper); + +const GodotEval = { + godot_js_eval__deps: ['$GodotRuntime'], + godot_js_eval__sig: 'iiiiiii', + godot_js_eval: function (p_js, p_use_global_ctx, p_union_ptr, p_byte_arr, p_byte_arr_write, p_callback) { + const js_code = GodotRuntime.parseString(p_js); + let eval_ret = null; + try { + if (p_use_global_ctx) { + // indirect eval call grants global execution context + const global_eval = eval; // eslint-disable-line no-eval + eval_ret = global_eval(js_code); + } else { + eval_ret = eval(js_code); // eslint-disable-line no-eval + } + } catch (e) { + GodotRuntime.error(e); + } + + switch (typeof eval_ret) { + case 'boolean': + GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'i32'); + return 1; // BOOL + + case 'number': + GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'double'); + return 3; // REAL + + case 'string': + GodotRuntime.setHeapValue(p_union_ptr, GodotRuntime.allocString(eval_ret), '*'); + return 4; // STRING + + case 'object': + if (eval_ret === null) { + break; + } + + if (ArrayBuffer.isView(eval_ret) && !(eval_ret instanceof Uint8Array)) { + eval_ret = new Uint8Array(eval_ret.buffer); + } else if (eval_ret instanceof ArrayBuffer) { + eval_ret = new Uint8Array(eval_ret); + } + if (eval_ret instanceof Uint8Array) { + const func = GodotRuntime.get_func(p_callback); + const bytes_ptr = func(p_byte_arr, p_byte_arr_write, eval_ret.length); + HEAPU8.set(eval_ret, bytes_ptr); + return 20; // POOL_BYTE_ARRAY + } + break; + + // no default + } + return 0; // NIL + }, +}; + +mergeInto(LibraryManager.library, GodotEval); diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 09e1f9461c..e7d3c9552e 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -164,7 +164,12 @@ bool OS_LinuxBSD::_check_internal_feature_support(const String &p_feature) { String OS_LinuxBSD::get_config_path() const { if (has_environment("XDG_CONFIG_HOME")) { - return get_environment("XDG_CONFIG_HOME"); + if (get_environment("XDG_CONFIG_HOME").is_abs_path()) { + return get_environment("XDG_CONFIG_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_CONFIG_HOME` is a relative path. Ignoring its value and falling back to `$HOME/.config` or `.` per the XDG Base Directory specification."); + return has_environment("HOME") ? get_environment("HOME").plus_file(".config") : "."; + } } else if (has_environment("HOME")) { return get_environment("HOME").plus_file(".config"); } else { @@ -174,7 +179,12 @@ String OS_LinuxBSD::get_config_path() const { String OS_LinuxBSD::get_data_path() const { if (has_environment("XDG_DATA_HOME")) { - return get_environment("XDG_DATA_HOME"); + if (get_environment("XDG_DATA_HOME").is_abs_path()) { + return get_environment("XDG_DATA_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_DATA_HOME` is a relative path. Ignoring its value and falling back to `$HOME/.local/share` or `get_config_path()` per the XDG Base Directory specification."); + return has_environment("HOME") ? get_environment("HOME").plus_file(".local/share") : get_config_path(); + } } else if (has_environment("HOME")) { return get_environment("HOME").plus_file(".local/share"); } else { @@ -184,7 +194,12 @@ String OS_LinuxBSD::get_data_path() const { String OS_LinuxBSD::get_cache_path() const { if (has_environment("XDG_CACHE_HOME")) { - return get_environment("XDG_CACHE_HOME"); + if (get_environment("XDG_CACHE_HOME").is_abs_path()) { + return get_environment("XDG_CACHE_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_CACHE_HOME` is a relative path. Ignoring its value and falling back to `$HOME/.cache` or `get_config_path()` per the XDG Base Directory specification."); + return has_environment("HOME") ? get_environment("HOME").plus_file(".cache") : get_config_path(); + } } else if (has_environment("HOME")) { return get_environment("HOME").plus_file(".cache"); } else { diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index e6feda5a9b..e199ea1b0d 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -188,8 +188,14 @@ MainLoop *OS_OSX::get_main_loop() const { } String OS_OSX::get_config_path() const { + // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on Windows as well. if (has_environment("XDG_CONFIG_HOME")) { - return get_environment("XDG_CONFIG_HOME"); + if (get_environment("XDG_CONFIG_HOME").is_abs_path()) { + return get_environment("XDG_CONFIG_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_CONFIG_HOME` is a relative path. Ignoring its value and falling back to `$HOME/Library/Application Support` or `.` per the XDG Base Directory specification."); + return has_environment("HOME") ? get_environment("HOME").plus_file("Library/Application Support") : "."; + } } else if (has_environment("HOME")) { return get_environment("HOME").plus_file("Library/Application Support"); } else { @@ -198,16 +204,28 @@ String OS_OSX::get_config_path() const { } String OS_OSX::get_data_path() const { + // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on Windows as well. if (has_environment("XDG_DATA_HOME")) { - return get_environment("XDG_DATA_HOME"); + if (get_environment("XDG_DATA_HOME").is_abs_path()) { + return get_environment("XDG_DATA_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_DATA_HOME` is a relative path. Ignoring its value and falling back to `get_config_path()` per the XDG Base Directory specification."); + return get_config_path(); + } } else { return get_config_path(); } } String OS_OSX::get_cache_path() const { + // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on Windows as well. if (has_environment("XDG_CACHE_HOME")) { - return get_environment("XDG_CACHE_HOME"); + if (get_environment("XDG_CACHE_HOME").is_abs_path()) { + return get_environment("XDG_CACHE_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_CACHE_HOME` is a relative path. Ignoring its value and falling back to `$HOME/Libary/Caches` or `get_config_path()` per the XDG Base Directory specification."); + return has_environment("HOME") ? get_environment("HOME").plus_file("Library/Caches") : get_config_path(); + } } else if (has_environment("HOME")) { return get_environment("HOME").plus_file("Library/Caches"); } else { diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index f517190a89..e0259882b0 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -631,8 +631,14 @@ MainLoop *OS_Windows::get_main_loop() const { } String OS_Windows::get_config_path() const { - if (has_environment("XDG_CONFIG_HOME")) { // unlikely, but after all why not? - return get_environment("XDG_CONFIG_HOME"); + // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on Windows as well. + if (has_environment("XDG_CONFIG_HOME")) { + if (get_environment("XDG_CONFIG_HOME").is_abs_path()) { + return get_environment("XDG_CONFIG_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_CONFIG_HOME` is a relative path. Ignoring its value and falling back to `%APPDATA%` or `.` per the XDG Base Directory specification."); + return has_environment("APPDATA") ? get_environment("APPDATA") : "."; + } } else if (has_environment("APPDATA")) { return get_environment("APPDATA"); } else { @@ -641,16 +647,28 @@ String OS_Windows::get_config_path() const { } String OS_Windows::get_data_path() const { + // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on Windows as well. if (has_environment("XDG_DATA_HOME")) { - return get_environment("XDG_DATA_HOME"); + if (get_environment("XDG_DATA_HOME").is_abs_path()) { + return get_environment("XDG_DATA_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_DATA_HOME` is a relative path. Ignoring its value and falling back to `get_config_path()` per the XDG Base Directory specification."); + return get_config_path(); + } } else { return get_config_path(); } } String OS_Windows::get_cache_path() const { + // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on Windows as well. if (has_environment("XDG_CACHE_HOME")) { - return get_environment("XDG_CACHE_HOME"); + if (get_environment("XDG_CACHE_HOME").is_abs_path()) { + return get_environment("XDG_CACHE_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_CACHE_HOME` is a relative path. Ignoring its value and falling back to `%TEMP%` or `get_config_path()` per the XDG Base Directory specification."); + return has_environment("TEMP") ? get_environment("TEMP") : get_config_path(); + } } else if (has_environment("TEMP")) { return get_environment("TEMP"); } else { diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 0afead0863..188f8d300d 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -64,13 +64,13 @@ int TileMapPattern::get_cell_source_id(const Vector2i &p_coords) const { } Vector2i TileMapPattern::get_cell_atlas_coords(const Vector2i &p_coords) const { - ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetAtlasSource::INVALID_ATLAS_COORDS); + ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_ATLAS_COORDS); return pattern[p_coords].get_atlas_coords(); } int TileMapPattern::get_cell_alternative_tile(const Vector2i &p_coords) const { - ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_TILE_ALTERNATIVE); return pattern[p_coords].alternative_tile; } @@ -113,7 +113,7 @@ void TileMapPattern::clear() { }; void TileMapPattern::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(-1), DEFVAL(TileSetAtlasSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetAtlasSource::INVALID_TILE_ALTERNATIVE)); + ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(-1), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE)); ClassDB::bind_method(D_METHOD("has_cell", "coords"), &TileMapPattern::has_cell); ClassDB::bind_method(D_METHOD("remove_cell", "coords"), &TileMapPattern::remove_cell); ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMapPattern::get_cell_source_id); @@ -520,12 +520,12 @@ void TileMap::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i Vector2i atlas_coords = p_atlas_coords; int alternative_tile = p_alternative_tile; - if ((source_id == -1 || atlas_coords == TileSetAtlasSource::INVALID_ATLAS_COORDS || alternative_tile == TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) && - (source_id != -1 || atlas_coords != TileSetAtlasSource::INVALID_ATLAS_COORDS || alternative_tile != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE)) { + if ((source_id == -1 || atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE) && + (source_id != -1 || atlas_coords != TileSetSource::INVALID_ATLAS_COORDS || alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE)) { WARN_PRINT("Setting a cell a cell as empty requires both source_id, atlas_coord and alternative_tile to be set to their respective \"invalid\" values. Values were thus changes accordingly."); source_id = -1; - atlas_coords = TileSetAtlasSource::INVALID_ATLAS_COORDS; - alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + atlas_coords = TileSetSource::INVALID_ATLAS_COORDS; + alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; } if (!E && source_id == -1) { @@ -602,7 +602,7 @@ Vector2i TileMap::get_cell_atlas_coords(const Vector2i &p_coords) const { const Map<Vector2i, TileMapCell>::Element *E = tile_map.find(p_coords); if (!E) { - return TileSetAtlasSource::INVALID_ATLAS_COORDS; + return TileSetSource::INVALID_ATLAS_COORDS; } return E->get().get_atlas_coords(); @@ -613,7 +613,7 @@ int TileMap::get_cell_alternative_tile(const Vector2i &p_coords) const { const Map<Vector2i, TileMapCell>::Element *E = tile_map.find(p_coords); if (!E) { - return TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + return TileSetSource::INVALID_TILE_ALTERNATIVE; } return E->get().alternative_tile; @@ -721,7 +721,7 @@ void TileMap::fix_invalid_tiles() { for (Map<Vector2i, TileMapCell>::Element *E = tile_map.front(); E; E = E->next()) { TileSetSource *source = *tile_set->get_source(E->get().source_id); if (!source || !source->has_tile(E->get().get_atlas_coords()) || !source->has_alternative_tile(E->get().get_atlas_coords(), E->get().alternative_tile)) { - set_cell(E->key(), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + set_cell(E->key(), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); } } } @@ -1716,7 +1716,7 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_quadrant_size", "size"), &TileMap::set_quadrant_size); ClassDB::bind_method(D_METHOD("get_quadrant_size"), &TileMap::get_quadrant_size); - ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMap::set_cell, DEFVAL(-1), DEFVAL(TileSetAtlasSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetAtlasSource::INVALID_TILE_ALTERNATIVE)); + ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMap::set_cell, DEFVAL(-1), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE)); ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMap::get_cell_source_id); ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "coords"), &TileMap::get_cell_atlas_coords); ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "coords"), &TileMap::get_cell_alternative_tile); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index e9dbccbdb9..2703e1980e 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -48,7 +48,7 @@ union TileMapCell { }; uint64_t _u64t; - TileMapCell(int p_source_id = -1, Vector2i p_atlas_coords = TileSetAtlasSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) { + TileMapCell(int p_source_id = -1, Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE) { source_id = p_source_id; set_atlas_coords(p_atlas_coords); alternative_tile = p_alternative_tile; @@ -112,16 +112,19 @@ struct TileMapQuadrant { // Debug. RID debug_canvas_item; - // Rendering + // Rendering. List<RID> canvas_items; List<RID> occluders; // Physics. List<RID> bodies; - // Navigation + // Navigation. Map<Vector2i, Vector<RID>> navigation_regions; + // Scenes. + Map<Vector2i, String> scenes; + void operator=(const TileMapQuadrant &q) { coords = q.coords; debug_canvas_item = q.debug_canvas_item; @@ -248,7 +251,7 @@ public: void set_quadrant_size(int p_size); int get_quadrant_size() const; - void set_cell(const Vector2i &p_coords, int p_source_id = -1, const Vector2i p_atlas_coords = TileSetAtlasSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + void set_cell(const Vector2i &p_coords, int p_source_id = -1, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE); int get_cell_source_id(const Vector2i &p_coords) const; Vector2i get_cell_atlas_coords(const Vector2i &p_coords) const; int get_cell_alternative_tile(const Vector2i &p_coords) const; diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index 93851aaedd..e895d18604 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -517,7 +517,7 @@ Vector3 RigidBody3D::get_angular_velocity() const { return angular_velocity; } -Basis RigidBody3D::get_inverse_inertia_tensor() { +Basis RigidBody3D::get_inverse_inertia_tensor() const { return inverse_inertia_tensor; } diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h index cf18b8ca82..818ff97730 100644 --- a/scene/3d/physics_body_3d.h +++ b/scene/3d/physics_body_3d.h @@ -183,7 +183,7 @@ public: void set_angular_velocity(const Vector3 &p_velocity); Vector3 get_angular_velocity() const override; - Basis get_inverse_inertia_tensor(); + Basis get_inverse_inertia_tensor() const; void set_gravity_scale(real_t p_gravity_scale); real_t get_gravity_scale() const; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index f24ea7e39b..b9c1659fd1 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -657,6 +657,7 @@ void register_scene_types() { ClassDB::register_class<TileSet>(); ClassDB::register_virtual_class<TileSetSource>(); ClassDB::register_class<TileSetAtlasSource>(); + ClassDB::register_class<TileSetScenesCollectionSource>(); ClassDB::register_class<TileData>(); ClassDB::register_class<TileMap>(); ClassDB::register_class<ParallaxBackground>(); diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index c4b8a56f54..3cc9627af3 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -106,15 +106,15 @@ void TileSet::_compute_next_source_id() { } // Sources management -int TileSet::add_source(Ref<TileSetAtlasSource> p_tile_atlas_source, int p_atlas_source_id_override) { - ERR_FAIL_COND_V(!p_tile_atlas_source.is_valid(), -1); +int TileSet::add_source(Ref<TileSetSource> p_tile_set_source, int p_atlas_source_id_override) { + ERR_FAIL_COND_V(!p_tile_set_source.is_valid(), -1); ERR_FAIL_COND_V_MSG(p_atlas_source_id_override >= 0 && (sources.has(p_atlas_source_id_override)), -1, vformat("Cannot create TileSet atlas source. Another atlas source exists with id %d.", p_atlas_source_id_override)); int new_source_id = p_atlas_source_id_override >= 0 ? p_atlas_source_id_override : next_source_id; - sources[new_source_id] = p_tile_atlas_source; + sources[new_source_id] = p_tile_set_source; source_ids.append(new_source_id); source_ids.sort(); - p_tile_atlas_source->set_tile_set(this); + p_tile_set_source->set_tile_set(this); _compute_next_source_id(); sources[new_source_id]->connect("changed", callable_mp(this, &TileSet::_source_changed)); @@ -668,8 +668,8 @@ void TileSet::reset_state() { custom_data_layers.clear(); } -const Vector2i TileSetAtlasSource::INVALID_ATLAS_COORDS = Vector2i(-1, -1); -const int TileSetAtlasSource::INVALID_TILE_ALTERNATIVE = -1; +const Vector2i TileSetSource::INVALID_ATLAS_COORDS = Vector2i(-1, -1); +const int TileSetSource::INVALID_TILE_ALTERNATIVE = -1; #ifndef DISABLE_DEPRECATED void TileSet::compatibility_conversion() { @@ -1086,7 +1086,7 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { return true; } } else if (components.size() == 2 && components[0] == "sources" && components[1].is_valid_integer()) { - // Create atlas if it does not exists. + // Create source only if it does not exists. int source_id = components[1].to_int(); if (!has_source(source_id)) { @@ -1399,10 +1399,11 @@ void TileSet::_bind_methods() { TileSet::TileSet() { // Instanciatie and list all plugins. - tile_set_plugins_vector.append(memnew(TileSetAtlasPluginRendering)); - tile_set_plugins_vector.append(memnew(TileSetAtlasPluginPhysics)); - tile_set_plugins_vector.append(memnew(TileSetAtlasPluginTerrain)); - tile_set_plugins_vector.append(memnew(TileSetAtlasPluginNavigation)); + tile_set_plugins_vector.append(memnew(TileSetPluginAtlasRendering)); + tile_set_plugins_vector.append(memnew(TileSetPluginAtlasPhysics)); + tile_set_plugins_vector.append(memnew(TileSetPluginAtlasTerrain)); + tile_set_plugins_vector.append(memnew(TileSetPluginAtlasNavigation)); + tile_set_plugins_vector.append(memnew(TileSetPluginScenesCollections)); } TileSet::~TileSet() { @@ -1530,13 +1531,13 @@ bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value) // Compute the vector2i if we have coordinates. Vector<String> coords_split = components[0].split(":"); - Vector2i coords = TileSetAtlasSource::INVALID_ATLAS_COORDS; + Vector2i coords = TileSetSource::INVALID_ATLAS_COORDS; if (coords_split.size() == 2 && coords_split[0].is_valid_integer() && coords_split[1].is_valid_integer()) { coords = Vector2i(coords_split[0].to_int(), coords_split[1].to_int()); } // Properties. - if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { // Create the tile if needed. if (!has_tile(coords)) { create_tile(coords); @@ -1549,7 +1550,7 @@ bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value) tiles[coords].next_alternative_id = p_value; } else if (components[1].is_valid_integer()) { int alternative_id = components[1].to_int(); - if (alternative_id != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) { + if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE) { // Create the alternative if needed ? if (!has_alternative_tile(coords, alternative_id)) { create_alternative_tile(coords, alternative_id); @@ -1594,7 +1595,7 @@ bool TileSetAtlasSource::_get(const StringName &p_name, Variant &r_ret) const { return true; } else if (components[1].is_valid_integer()) { int alternative_id = components[1].to_int(); - if (alternative_id != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE && tiles[coords].alternatives.has(alternative_id)) { + if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE && tiles[coords].alternatives.has(alternative_id)) { if (components.size() >= 3) { bool valid; r_ret = tiles[coords].alternatives[alternative_id]->get(components[2], &valid); @@ -1745,7 +1746,7 @@ int TileSetAtlasSource::get_tiles_count() const { } Vector2i TileSetAtlasSource::get_tile_id(int p_index) const { - ERR_FAIL_INDEX_V(p_index, tiles_ids.size(), TileSetAtlasSource::INVALID_ATLAS_COORDS); + ERR_FAIL_INDEX_V(p_index, tiles_ids.size(), TileSetSource::INVALID_ATLAS_COORDS); return tiles_ids[p_index]; } @@ -1798,7 +1799,7 @@ bool TileSetAtlasSource::can_move_tile_in_atlas(Vector2i p_atlas_coords, Vector2 for (int x = new_rect.position.x; x < new_rect.get_end().x; x++) { for (int y = new_rect.position.y; y < new_rect.get_end().y; y++) { Vector2i coords = get_tile_at_coords(Vector2i(x, y)); - if (coords != p_atlas_coords && coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + if (coords != p_atlas_coords && coords != TileSetSource::INVALID_ATLAS_COORDS) { return false; } } @@ -1880,7 +1881,7 @@ void TileSetAtlasSource::clear_tiles_outside_texture() { int TileSetAtlasSource::create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override) { ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), -1, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); - ERR_FAIL_COND_V_MSG(p_alternative_id_override >= 0 && (tiles[p_atlas_coords].alternatives.has(p_alternative_id_override) || tiles[p_atlas_coords].alternatives.has(p_alternative_id_override)), -1, vformat("Cannot create alternative tile. Another alternative exists with id %d.", p_alternative_id_override)); + ERR_FAIL_COND_V_MSG(p_alternative_id_override >= 0 && tiles[p_atlas_coords].alternatives.has(p_alternative_id_override), -1, vformat("Cannot create alternative tile. Another alternative exists with id %d.", p_alternative_id_override)); int new_alternative_id = p_alternative_id_override >= 0 ? p_alternative_id_override : tiles[p_atlas_coords].next_alternative_id; @@ -2034,6 +2035,202 @@ void TileSetAtlasSource::_compute_next_alternative_id(const Vector2i p_atlas_coo }; } +/////////////////////////////// TileSetScenesCollectionSource ////////////////////////////////////// + +void TileSetScenesCollectionSource::_compute_next_alternative_id() { + while (scenes.has(next_scene_id)) { + next_scene_id = (next_scene_id % 1073741823) + 1; // 2 ** 30 + }; +} + +int TileSetScenesCollectionSource::get_tiles_count() const { + return 1; +} + +Vector2i TileSetScenesCollectionSource::get_tile_id(int p_tile_index) const { + ERR_FAIL_COND_V(p_tile_index != 0, TileSetSource::INVALID_ATLAS_COORDS); + return Vector2i(); +} + +bool TileSetScenesCollectionSource::has_tile(Vector2i p_atlas_coords) const { + return p_atlas_coords == Vector2i(); +} + +int TileSetScenesCollectionSource::get_alternative_tiles_count(const Vector2i p_atlas_coords) const { + return scenes_ids.size(); +} + +int TileSetScenesCollectionSource::get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const { + ERR_FAIL_COND_V(p_atlas_coords != Vector2i(), TileSetSource::INVALID_TILE_ALTERNATIVE); + ERR_FAIL_INDEX_V(p_index, scenes_ids.size(), TileSetSource::INVALID_TILE_ALTERNATIVE); + + return scenes_ids[p_index]; +} + +bool TileSetScenesCollectionSource::has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const { + ERR_FAIL_COND_V(p_atlas_coords != Vector2i(), false); + return scenes.has(p_alternative_tile); +} + +int TileSetScenesCollectionSource::create_scene_tile(Ref<PackedScene> p_packed_scene, int p_id_override) { + ERR_FAIL_COND_V_MSG(p_id_override >= 0 && scenes.has(p_id_override), -1, vformat("Cannot create scene tile. Another scene tile exists with id %d.", p_id_override)); + + int new_scene_id = p_id_override >= 0 ? p_id_override : next_scene_id; + + scenes[new_scene_id] = SceneData(); + scenes_ids.append(new_scene_id); + scenes_ids.sort(); + set_scene_tile_scene(new_scene_id, p_packed_scene); + _compute_next_alternative_id(); + + emit_signal("changed"); + + return new_scene_id; +} + +void TileSetScenesCollectionSource::set_scene_tile_id(int p_id, int p_new_id) { + ERR_FAIL_COND(p_new_id < 0); + ERR_FAIL_COND(!has_scene_tile_id(p_id)); + ERR_FAIL_COND(has_scene_tile_id(p_new_id)); + + scenes[p_new_id] = SceneData(); + scenes[p_new_id] = scenes[p_id]; + scenes_ids.append(p_new_id); + scenes_ids.sort(); + + _compute_next_alternative_id(); + + scenes.erase(p_id); + scenes_ids.erase(p_id); + + emit_signal("changed"); +} + +void TileSetScenesCollectionSource::set_scene_tile_scene(int p_id, Ref<PackedScene> p_packed_scene) { + ERR_FAIL_COND(!scenes.has(p_id)); + if (p_packed_scene.is_valid()) { + // Make sure we have a root node. Supposed to be at 0 index because find_node_by_path() does not seem to work. + ERR_FAIL_COND(!p_packed_scene->get_state().is_valid()); + ERR_FAIL_COND(p_packed_scene->get_state()->get_node_count() < 1); + + // Check if it extends CanvasItem. + String type = p_packed_scene->get_state()->get_node_type(0); + bool extends_correct_class = ClassDB::is_parent_class(type, "Control") || ClassDB::is_parent_class(type, "Node2D"); + ERR_FAIL_COND_MSG(!extends_correct_class, vformat("Invalid PackedScene for TileSetScenesCollectionSource: %s. Root node should extend Control or Node2D.", p_packed_scene->get_path())); + + scenes[p_id].scene = p_packed_scene; + } else { + scenes[p_id].scene = Ref<PackedScene>(); + } + emit_signal("changed"); +} + +Ref<PackedScene> TileSetScenesCollectionSource::get_scene_tile_scene(int p_id) const { + ERR_FAIL_COND_V(!scenes.has(p_id), Ref<PackedScene>()); + return scenes[p_id].scene; +} + +void TileSetScenesCollectionSource::set_scene_tile_display_placeholder(int p_id, bool p_display_placeholder) { + ERR_FAIL_COND(!scenes.has(p_id)); + + scenes[p_id].display_placeholder = p_display_placeholder; + + emit_signal("changed"); +} + +bool TileSetScenesCollectionSource::get_scene_tile_display_placeholder(int p_id) const { + ERR_FAIL_COND_V(!scenes.has(p_id), false); + return scenes[p_id].display_placeholder; +} + +void TileSetScenesCollectionSource::remove_scene_tile(int p_id) { + ERR_FAIL_COND(!scenes.has(p_id)); + + scenes.erase(p_id); + scenes_ids.erase(p_id); + emit_signal("changed"); +} + +int TileSetScenesCollectionSource::get_next_scene_tile_id() const { + return next_scene_id; +} + +bool TileSetScenesCollectionSource::_set(const StringName &p_name, const Variant &p_value) { + Vector<String> components = String(p_name).split("/", true, 2); + + if (components.size() >= 2 && components[0] == "scenes" && components[1].is_valid_integer()) { + int scene_id = components[1].to_int(); + if (components.size() >= 3 && components[2] == "scene") { + if (has_scene_tile_id(scene_id)) { + set_scene_tile_scene(scene_id, p_value); + } else { + create_scene_tile(p_value, scene_id); + } + return true; + } else if (components.size() >= 3 && components[2] == "display_placeholder") { + if (!has_scene_tile_id(scene_id)) { + create_scene_tile(p_value, scene_id); + } + + return true; + } + } + + return false; +} + +bool TileSetScenesCollectionSource::_get(const StringName &p_name, Variant &r_ret) const { + Vector<String> components = String(p_name).split("/", true, 2); + + if (components.size() >= 2 && components[0] == "scenes" && components[1].is_valid_integer() && scenes.has(components[1].to_int())) { + if (components.size() >= 3 && components[2] == "scene") { + r_ret = scenes[components[1].to_int()].scene; + return true; + } else if (components.size() >= 3 && components[2] == "display_placeholder") { + r_ret = scenes[components[1].to_int()].scene; + return true; + } + } + + return false; +} + +void TileSetScenesCollectionSource::_get_property_list(List<PropertyInfo> *p_list) const { + for (int i = 0; i < scenes_ids.size(); i++) { + p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("scenes/%d/scene", scenes_ids[i]), PROPERTY_HINT_RESOURCE_TYPE, "TileSetScenesCollectionSource")); + + PropertyInfo property_info = PropertyInfo(Variant::BOOL, vformat("scenes/%d/display_placeholder", scenes_ids[i])); + if (scenes[scenes_ids[i]].display_placeholder == false) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + } +} + +void TileSetScenesCollectionSource::_bind_methods() { + // Base tiles + ClassDB::bind_method(D_METHOD("get_tiles_count"), &TileSetScenesCollectionSource::get_tiles_count); + ClassDB::bind_method(D_METHOD("get_tile_id", "index"), &TileSetScenesCollectionSource::get_tile_id); + ClassDB::bind_method(D_METHOD("has_tile", "atlas_coords"), &TileSetScenesCollectionSource::has_tile); + + // Alternative tiles + ClassDB::bind_method(D_METHOD("get_alternative_tiles_count", "atlas_coords"), &TileSetScenesCollectionSource::get_alternative_tiles_count); + ClassDB::bind_method(D_METHOD("get_alternative_tile_id", "atlas_coords", "index"), &TileSetScenesCollectionSource::get_alternative_tile_id); + ClassDB::bind_method(D_METHOD("has_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetScenesCollectionSource::has_alternative_tile); + + ClassDB::bind_method(D_METHOD("get_scene_tiles_count"), &TileSetScenesCollectionSource::get_scene_tiles_count); + ClassDB::bind_method(D_METHOD("get_scene_tile_id", "index"), &TileSetScenesCollectionSource::get_scene_tile_id); + ClassDB::bind_method(D_METHOD("has_scene_tile_id", "id"), &TileSetScenesCollectionSource::has_scene_tile_id); + ClassDB::bind_method(D_METHOD("create_scene_tile", "packed_scene", "id_override"), &TileSetScenesCollectionSource::create_scene_tile, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("set_scene_tile_id", "id", "new_id"), &TileSetScenesCollectionSource::set_scene_tile_id); + ClassDB::bind_method(D_METHOD("set_scene_tile_scene", "id", "packed_scene"), &TileSetScenesCollectionSource::set_scene_tile_scene); + ClassDB::bind_method(D_METHOD("get_scene_tile_scene", "id"), &TileSetScenesCollectionSource::get_scene_tile_scene); + ClassDB::bind_method(D_METHOD("set_scene_tile_display_placeholder", "id", "display_placeholder"), &TileSetScenesCollectionSource::set_scene_tile_display_placeholder); + ClassDB::bind_method(D_METHOD("get_scene_tile_display_placeholder", "id"), &TileSetScenesCollectionSource::get_scene_tile_display_placeholder); + ClassDB::bind_method(D_METHOD("remove_scene_tile", "id"), &TileSetScenesCollectionSource::remove_scene_tile); + ClassDB::bind_method(D_METHOD("get_next_scene_tile_id"), &TileSetScenesCollectionSource::get_next_scene_tile_id); +} + /////////////////////////////// TileData ////////////////////////////////////// void TileData::set_tile_set(const TileSet *p_tile_set) { @@ -2834,10 +3031,10 @@ void TileData::_bind_methods() { ADD_SIGNAL(MethodInfo("changed")); } -/////////////////////////////// TileSetAtlasPluginTerrain ////////////////////////////////////// +/////////////////////////////// TileSetPluginAtlasTerrain ////////////////////////////////////// // --- PLUGINS --- -void TileSetAtlasPluginTerrain::_draw_square_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { +void TileSetPluginAtlasTerrain::_draw_square_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { Rect2 bit_rect; bit_rect.size = Vector2(p_size) / 3; switch (p_bit) { @@ -2872,7 +3069,7 @@ void TileSetAtlasPluginTerrain::_draw_square_corner_or_side_terrain_bit(CanvasIt p_canvas_item->draw_rect(bit_rect, p_color); } -void TileSetAtlasPluginTerrain::_draw_square_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { +void TileSetPluginAtlasTerrain::_draw_square_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { PackedColorArray color_array; color_array.push_back(p_color); @@ -2919,7 +3116,7 @@ void TileSetAtlasPluginTerrain::_draw_square_corner_terrain_bit(CanvasItem *p_ca } } -void TileSetAtlasPluginTerrain::_draw_square_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { +void TileSetPluginAtlasTerrain::_draw_square_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { PackedColorArray color_array; color_array.push_back(p_color); @@ -2958,7 +3155,7 @@ void TileSetAtlasPluginTerrain::_draw_square_side_terrain_bit(CanvasItem *p_canv } } -void TileSetAtlasPluginTerrain::_draw_isometric_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { +void TileSetPluginAtlasTerrain::_draw_isometric_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { PackedColorArray color_array; color_array.push_back(p_color); @@ -3021,7 +3218,7 @@ void TileSetAtlasPluginTerrain::_draw_isometric_corner_or_side_terrain_bit(Canva } } -void TileSetAtlasPluginTerrain::_draw_isometric_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { +void TileSetPluginAtlasTerrain::_draw_isometric_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { PackedColorArray color_array; color_array.push_back(p_color); @@ -3068,7 +3265,7 @@ void TileSetAtlasPluginTerrain::_draw_isometric_corner_terrain_bit(CanvasItem *p } } -void TileSetAtlasPluginTerrain::_draw_isometric_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { +void TileSetPluginAtlasTerrain::_draw_isometric_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { PackedColorArray color_array; color_array.push_back(p_color); @@ -3107,7 +3304,7 @@ void TileSetAtlasPluginTerrain::_draw_isometric_side_terrain_bit(CanvasItem *p_c } } -void TileSetAtlasPluginTerrain::_draw_half_offset_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { +void TileSetPluginAtlasTerrain::_draw_half_offset_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { PackedColorArray color_array; color_array.push_back(p_color); @@ -3272,7 +3469,7 @@ void TileSetAtlasPluginTerrain::_draw_half_offset_corner_or_side_terrain_bit(Can } } -void TileSetAtlasPluginTerrain::_draw_half_offset_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { +void TileSetPluginAtlasTerrain::_draw_half_offset_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { PackedColorArray color_array; color_array.push_back(p_color); @@ -3383,7 +3580,7 @@ void TileSetAtlasPluginTerrain::_draw_half_offset_corner_terrain_bit(CanvasItem } } -void TileSetAtlasPluginTerrain::_draw_half_offset_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { +void TileSetPluginAtlasTerrain::_draw_half_offset_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { PackedColorArray color_array; color_array.push_back(p_color); @@ -3498,7 +3695,7 @@ void TileSetAtlasPluginTerrain::_draw_half_offset_side_terrain_bit(CanvasItem *p } \ } -void TileSetAtlasPluginTerrain::draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, const TileData *p_tile_data) { +void TileSetPluginAtlasTerrain::draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, const TileData *p_tile_data) { ERR_FAIL_COND(!p_tile_set); ERR_FAIL_COND(!p_tile_data); @@ -3632,9 +3829,9 @@ void TileSetAtlasPluginTerrain::draw_terrains(CanvasItem *p_canvas_item, Transfo RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); } -/////////////////////////////// TileSetAtlasPluginRendering ////////////////////////////////////// +/////////////////////////////// TileSetPluginAtlasRendering ////////////////////////////////////// -void TileSetAtlasPluginRendering::tilemap_notification(TileMap *p_tile_map, int p_what) { +void TileSetPluginAtlasRendering::tilemap_notification(TileMap *p_tile_map, int p_what) { switch (p_what) { case CanvasItem::NOTIFICATION_VISIBILITY_CHANGED: { bool visible = p_tile_map->is_visible_in_tree(); @@ -3672,7 +3869,7 @@ void TileSetAtlasPluginRendering::tilemap_notification(TileMap *p_tile_map, int } } -void TileSetAtlasPluginRendering::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation) { +void TileSetPluginAtlasRendering::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation) { ERR_FAIL_COND(!p_tile_set.is_valid()); ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id)); ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords)); @@ -3687,6 +3884,12 @@ void TileSetAtlasPluginRendering::draw_tile(RID p_canvas_item, Vector2i p_positi return; } + // Check if we are in the texture, return otherwise. + Vector2i grid_size = atlas_source->get_atlas_grid_size(); + if (p_atlas_coords.x >= grid_size.x || p_atlas_coords.y >= grid_size.y) { + return; + } + // Get tile data. TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile)); @@ -3724,7 +3927,7 @@ void TileSetAtlasPluginRendering::draw_tile(RID p_canvas_item, Vector2i p_positi } } -void TileSetAtlasPluginRendering::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { +void TileSetPluginAtlasRendering::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { ERR_FAIL_COND(!p_tile_map); ERR_FAIL_COND(!p_tile_map->is_inside_tree()); Ref<TileSet> tile_set = p_tile_map->get_tileset(); @@ -3858,14 +4061,14 @@ void TileSetAtlasPluginRendering::update_dirty_quadrants(TileMap *p_tile_map, Se } } -void TileSetAtlasPluginRendering::create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { +void TileSetPluginAtlasRendering::create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { Ref<TileSet> tile_set = p_tile_map->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); quadrant_order_dirty = true; } -void TileSetAtlasPluginRendering::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { +void TileSetPluginAtlasRendering::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { // Free the canvas items. for (List<RID>::Element *E = p_quadrant->canvas_items.front(); E; E = E->next()) { RenderingServer::get_singleton()->free(E->get()); @@ -3879,9 +4082,60 @@ void TileSetAtlasPluginRendering::cleanup_quadrant(TileMap *p_tile_map, TileMapQ p_quadrant->occluders.clear(); } -/////////////////////////////// TileSetAtlasPluginPhysics ////////////////////////////////////// +void TileSetPluginAtlasRendering::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { + Ref<TileSet> tile_set = p_tile_map->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(); + Vector2 quadrant_pos = p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size()); + for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { + const TileMapCell &c = p_tile_map->get_cell(E_cell->get()); -void TileSetAtlasPluginPhysics::tilemap_notification(TileMap *p_tile_map, int p_what) { + 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)) { + continue; + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + Vector2i grid_size = atlas_source->get_atlas_grid_size(); + if (!atlas_source->get_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 xform; + xform.set_origin(p_tile_map->map_to_world(E_cell->get()) - quadrant_pos); + rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); + rs->canvas_item_add_circle(p_quadrant->debug_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color); + } + } + } + } +} + +/////////////////////////////// TileSetPluginAtlasPhysics ////////////////////////////////////// + +void TileSetPluginAtlasPhysics::tilemap_notification(TileMap *p_tile_map, int p_what) { switch (p_what) { case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: { // Update the bodies transforms. @@ -3905,7 +4159,7 @@ void TileSetAtlasPluginPhysics::tilemap_notification(TileMap *p_tile_map, int p_ } } -void TileSetAtlasPluginPhysics::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { +void TileSetPluginAtlasPhysics::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { ERR_FAIL_COND(!p_tile_map); ERR_FAIL_COND(!p_tile_map->is_inside_tree()); Ref<TileSet> tile_set = p_tile_map->get_tileset(); @@ -3971,7 +4225,7 @@ void TileSetAtlasPluginPhysics::update_dirty_quadrants(TileMap *p_tile_map, Self } } -void TileSetAtlasPluginPhysics::create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { +void TileSetPluginAtlasPhysics::create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { Ref<TileSet> tile_set = p_tile_map->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); @@ -4016,7 +4270,7 @@ void TileSetAtlasPluginPhysics::create_quadrant(TileMap *p_tile_map, TileMapQuad } } -void TileSetAtlasPluginPhysics::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { +void TileSetPluginAtlasPhysics::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { // Remove a quadrant. for (int body_index = 0; body_index < p_quadrant->bodies.size(); body_index++) { PhysicsServer2D::get_singleton()->free(p_quadrant->bodies[body_index]); @@ -4024,7 +4278,7 @@ void TileSetAtlasPluginPhysics::cleanup_quadrant(TileMap *p_tile_map, TileMapQua p_quadrant->bodies.clear(); } -void TileSetAtlasPluginPhysics::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { +void TileSetPluginAtlasPhysics::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { // Draw the debug collision shapes. Ref<TileSet> tile_set = p_tile_map->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); @@ -4071,9 +4325,9 @@ void TileSetAtlasPluginPhysics::draw_quadrant_debug(TileMap *p_tile_map, TileMap } }; -/////////////////////////////// TileSetAtlasPluginNavigation ////////////////////////////////////// +/////////////////////////////// TileSetPluginAtlasNavigation ////////////////////////////////////// -void TileSetAtlasPluginNavigation::tilemap_notification(TileMap *p_tile_map, int p_what) { +void TileSetPluginAtlasNavigation::tilemap_notification(TileMap *p_tile_map, int p_what) { switch (p_what) { case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: { if (p_tile_map->is_inside_tree()) { @@ -4098,7 +4352,7 @@ void TileSetAtlasPluginNavigation::tilemap_notification(TileMap *p_tile_map, int } } -void TileSetAtlasPluginNavigation::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { +void TileSetPluginAtlasNavigation::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { ERR_FAIL_COND(!p_tile_map); ERR_FAIL_COND(!p_tile_map->is_inside_tree()); Ref<TileSet> tile_set = p_tile_map->get_tileset(); @@ -4169,7 +4423,7 @@ void TileSetAtlasPluginNavigation::update_dirty_quadrants(TileMap *p_tile_map, S } } -void TileSetAtlasPluginNavigation::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { +void TileSetPluginAtlasNavigation::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { // Clear navigation shapes in the quadrant. for (Map<Vector2i, Vector<RID>>::Element *E = p_quadrant->navigation_regions.front(); E; E = E->next()) { for (int i = 0; i < E->get().size(); i++) { @@ -4183,7 +4437,7 @@ void TileSetAtlasPluginNavigation::cleanup_quadrant(TileMap *p_tile_map, TileMap p_quadrant->navigation_regions.clear(); } -void TileSetAtlasPluginNavigation::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { +void TileSetPluginAtlasNavigation::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { // Draw the debug collision shapes. Ref<TileSet> tile_set = p_tile_map->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); @@ -4240,11 +4494,129 @@ void TileSetAtlasPluginNavigation::draw_quadrant_debug(TileMap *p_tile_map, Tile Vector<Color> colors; colors.push_back(random_variation_color); - RS::get_singleton()->canvas_item_add_polygon(p_quadrant->debug_canvas_item, vertices, colors); + rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, vertices, colors); + } + } + } + } + } + } +} + +/////////////////////////////// TileSetPluginScenesCollections ////////////////////////////////////// + +void TileSetPluginScenesCollections::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + Ref<TileSet> tile_set = p_tile_map->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); + + SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); + while (q_list_element) { + TileMapQuadrant &q = *q_list_element->self(); + + // Clear the scenes. + for (Map<Vector2i, String>::Element *E = q.scenes.front(); E; E = E->next()) { + Node *node = p_tile_map->get_node(E->get()); + if (node) { + node->queue_delete(); + } + } + + q.scenes.clear(); + + // Recreate the scenes. + for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) { + const TileMapCell &c = p_tile_map->get_cell(E_cell->get()); + + 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)) { + continue; + } + + 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->instance(); + p_tile_map->add_child(scene); + 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(p_tile_map->map_to_world(E_cell->get()) + scene_as_control->get_position()); + } else if (scene_as_node2d) { + Transform2D xform; + xform.set_origin(p_tile_map->map_to_world(E_cell->get())); + scene_as_node2d->set_transform(xform * scene_as_node2d->get_transform()); } + q.scenes[E_cell->get()] = scene->get_name(); } } } } + + q_list_element = q_list_element->next(); + } +} + +void TileSetPluginScenesCollections::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { + // Clear the scenes. + for (Map<Vector2i, String>::Element *E = p_quadrant->scenes.front(); E; E = E->next()) { + Node *node = p_tile_map->get_node(E->get()); + if (node) { + node->queue_delete(); + } + } + + p_quadrant->scenes.clear(); +} + +void TileSetPluginScenesCollections::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { + Ref<TileSet> tile_set = p_tile_map->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(); + Vector2 quadrant_pos = p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size()); + for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { + const TileMapCell &c = p_tile_map->get_cell(E_cell->get()); + + 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)) { + continue; + } + + 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 xform; + xform.set_origin(p_tile_map->map_to_world(E_cell->get()) - quadrant_pos); + rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); + rs->canvas_item_add_circle(p_quadrant->debug_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color); + } + } + } } } diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 20cf183a20..ae80299d85 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -57,10 +57,10 @@ class TileData; // Forward-declare the plugins. class TileSetPlugin; -class TileSetAtlasPluginRendering; -class TileSetAtlasPluginPhysics; -class TileSetAtlasPluginNavigation; -class TileSetAtlasPluginTerrain; +class TileSetPluginAtlasRendering; +class TileSetPluginAtlasPhysics; +class TileSetPluginAtlasNavigation; +class TileSetPluginAtlasTerrain; class TileSet : public Resource { GDCLASS(TileSet, Resource); @@ -264,7 +264,7 @@ public: int get_next_source_id() const; int get_source_count() const; int get_source_id(int p_index) const; - int add_source(Ref<TileSetAtlasSource> p_tile_atlas_source, int p_source_id_override = -1); + int add_source(Ref<TileSetSource> p_tile_set_source, int p_source_id_override = -1); void set_source_id(int p_source_id, int p_new_id); void remove_source(int p_source_id); bool has_source(int p_source_id) const; @@ -338,6 +338,9 @@ protected: const TileSet *tile_set = nullptr; public: + static const Vector2i INVALID_ATLAS_COORDS; // Vector2i(-1, -1); + static const int INVALID_TILE_ALTERNATIVE; // -1; + // Not exposed. virtual void set_tile_set(const TileSet *p_tile_set); virtual void notify_tile_data_properties_should_change(){}; @@ -358,9 +361,6 @@ class TileSetAtlasSource : public TileSetSource { GDCLASS(TileSetAtlasSource, TileSetSource); public: - static const Vector2i INVALID_ATLAS_COORDS; // Vector2i(-1, -1); - static const int INVALID_TILE_ALTERNATIVE; // -1; - struct TileAlternativesData { Vector2i size_in_atlas = Vector2i(1, 1); Vector2i texture_offset; @@ -443,6 +443,52 @@ public: ~TileSetAtlasSource(); }; +class TileSetScenesCollectionSource : public TileSetSource { + GDCLASS(TileSetScenesCollectionSource, TileSetSource); + +private: + struct SceneData { + Ref<PackedScene> scene; + bool display_placeholder = false; + }; + Vector<int> scenes_ids; + Map<int, SceneData> scenes; + int next_scene_id = 1; + + void _compute_next_alternative_id(); + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + + static void _bind_methods(); + +public: + // Tiles. + int get_tiles_count() const override; + Vector2i get_tile_id(int p_tile_index) const override; + bool has_tile(Vector2i p_atlas_coords) const override; + + // Alternative tiles. + int get_alternative_tiles_count(const Vector2i p_atlas_coords) const override; + int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const override; + bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const override; + + // Scenes sccessors. Lot are similar to "Alternative tiles". + int get_scene_tiles_count() { return get_alternative_tiles_count(Vector2i()); } + int get_scene_tile_id(int p_index) { return get_alternative_tile_id(Vector2i(), p_index); }; + bool has_scene_tile_id(int p_id) { return has_alternative_tile(Vector2i(), p_id); }; + int create_scene_tile(Ref<PackedScene> p_packed_scene = Ref<PackedScene>(), int p_id_override = -1); + void set_scene_tile_id(int p_id, int p_new_id); + void set_scene_tile_scene(int p_id, Ref<PackedScene> p_packed_scene); + Ref<PackedScene> get_scene_tile_scene(int p_id) const; + void set_scene_tile_display_placeholder(int p_id, bool p_packed_scene); + bool get_scene_tile_display_placeholder(int p_id) const; + void remove_scene_tile(int p_id); + int get_next_scene_tile_id() const; +}; + class TileData : public Object { GDCLASS(TileData, Object); @@ -572,8 +618,8 @@ public: virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant){}; }; -class TileSetAtlasPluginRendering : public TileSetPlugin { - GDCLASS(TileSetAtlasPluginRendering, TileSetPlugin); +class TileSetPluginAtlasRendering : public TileSetPlugin { + GDCLASS(TileSetPluginAtlasRendering, TileSetPlugin); private: static constexpr float fp_adjust = 0.00001; @@ -585,13 +631,14 @@ public: virtual void update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) override; virtual void create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; virtual void cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; + virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; // Other. static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0)); }; -class TileSetAtlasPluginTerrain : public TileSetPlugin { - GDCLASS(TileSetAtlasPluginTerrain, TileSetPlugin); +class TileSetPluginAtlasTerrain : public TileSetPlugin { + GDCLASS(TileSetPluginAtlasTerrain, TileSetPlugin); private: static void _draw_square_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit); @@ -607,13 +654,11 @@ private: static void _draw_half_offset_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis); public: - //virtual void tilemap_notification(const TileMap * p_tile_map, int p_what); - static void draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, const TileData *p_tile_data); }; -class TileSetAtlasPluginPhysics : public TileSetPlugin { - GDCLASS(TileSetAtlasPluginPhysics, TileSetPlugin); +class TileSetPluginAtlasPhysics : public TileSetPlugin { + GDCLASS(TileSetPluginAtlasPhysics, TileSetPlugin); public: // Tilemap updates @@ -624,14 +669,23 @@ public: virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; }; -class TileSetAtlasPluginNavigation : public TileSetPlugin { - GDCLASS(TileSetAtlasPluginNavigation, TileSetPlugin); +class TileSetPluginAtlasNavigation : public TileSetPlugin { + GDCLASS(TileSetPluginAtlasNavigation, TileSetPlugin); public: // Tilemap updates virtual void tilemap_notification(TileMap *p_tile_map, int p_what) override; virtual void update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) override; - //virtual void create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; + virtual void cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; + virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; +}; + +class TileSetPluginScenesCollections : public TileSetPlugin { + GDCLASS(TileSetPluginScenesCollections, TileSetPlugin); + +public: + // Tilemap updates + virtual void update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) override; virtual void cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; }; diff --git a/servers/text_server.cpp b/servers/text_server.cpp index 8b03565291..daed612b02 100644 --- a/servers/text_server.cpp +++ b/servers/text_server.cpp @@ -987,7 +987,7 @@ Vector<Vector2> TextServer::shaped_text_get_selection(RID p_shaped, int p_start, while (i < ranges.size()) { int j = i + 1; while (j < ranges.size()) { - if (Math::is_equal_approx(ranges[i].y, ranges[j].x, UNIT_EPSILON)) { + if (Math::is_equal_approx(ranges[i].y, ranges[j].x, (real_t)UNIT_EPSILON)) { ranges.write[i].y = ranges[j].y; ranges.remove(j); continue; diff --git a/tests/test_color.h b/tests/test_color.h index eb8d7dcbd4..ad4a7cd3f2 100644 --- a/tests/test_color.h +++ b/tests/test_color.h @@ -101,13 +101,13 @@ TEST_CASE("[Color] Reading methods") { const Color dark_blue = Color(0, 0, 0.5, 0.4); CHECK_MESSAGE( - Math::is_equal_approx(dark_blue.get_h(), 240 / 360.0), + Math::is_equal_approx(dark_blue.get_h(), 240.0f / 360.0f), "The returned HSV hue should match the expected value."); CHECK_MESSAGE( - Math::is_equal_approx(dark_blue.get_s(), 1), + Math::is_equal_approx(dark_blue.get_s(), 1.0f), "The returned HSV saturation should match the expected value."); CHECK_MESSAGE( - Math::is_equal_approx(dark_blue.get_v(), 0.5), + Math::is_equal_approx(dark_blue.get_v(), 0.5f), "The returned HSV value should match the expected value."); } diff --git a/tests/test_curve.h b/tests/test_curve.h index 019941a7ce..3055cfd97b 100644 --- a/tests/test_curve.h +++ b/tests/test_curve.h @@ -83,13 +83,13 @@ TEST_CASE("[Curve] Custom curve with free tangents") { Math::is_equal_approx(curve->interpolate(-0.1), 0), "Custom free curve should return the expected value at offset 0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate(0.1), 0.352), + Math::is_equal_approx(curve->interpolate(0.1), (real_t)0.352), "Custom free curve should return the expected value at offset 0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate(0.4), 0.352), + Math::is_equal_approx(curve->interpolate(0.4), (real_t)0.352), "Custom free curve should return the expected value at offset 0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate(0.7), 0.896), + Math::is_equal_approx(curve->interpolate(0.7), (real_t)0.896), "Custom free curve should return the expected value at offset 0.1."); CHECK_MESSAGE( Math::is_equal_approx(curve->interpolate(1), 1), @@ -102,13 +102,13 @@ TEST_CASE("[Curve] Custom curve with free tangents") { Math::is_equal_approx(curve->interpolate_baked(-0.1), 0), "Custom free curve should return the expected baked value at offset 0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate_baked(0.1), 0.352), + Math::is_equal_approx(curve->interpolate_baked(0.1), (real_t)0.352), "Custom free curve should return the expected baked value at offset 0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate_baked(0.4), 0.352), + Math::is_equal_approx(curve->interpolate_baked(0.4), (real_t)0.352), "Custom free curve should return the expected baked value at offset 0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate_baked(0.7), 0.896), + Math::is_equal_approx(curve->interpolate_baked(0.7), (real_t)0.896), "Custom free curve should return the expected baked value at offset 0.1."); CHECK_MESSAGE( Math::is_equal_approx(curve->interpolate_baked(1), 1), @@ -172,13 +172,13 @@ TEST_CASE("[Curve] Custom curve with linear tangents") { Math::is_equal_approx(curve->interpolate(-0.1), 0), "Custom linear curve should return the expected value at offset -0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate(0.1), 0.4), + Math::is_equal_approx(curve->interpolate(0.1), (real_t)0.4), "Custom linear curve should return the expected value at offset 0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate(0.4), 0.4), + Math::is_equal_approx(curve->interpolate(0.4), (real_t)0.4), "Custom linear curve should return the expected value at offset 0.4."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate(0.7), 0.8), + Math::is_equal_approx(curve->interpolate(0.7), (real_t)0.8), "Custom linear curve should return the expected value at offset 0.7."); CHECK_MESSAGE( Math::is_equal_approx(curve->interpolate(1), 1), @@ -191,13 +191,13 @@ TEST_CASE("[Curve] Custom curve with linear tangents") { Math::is_equal_approx(curve->interpolate_baked(-0.1), 0), "Custom linear curve should return the expected baked value at offset -0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate_baked(0.1), 0.4), + Math::is_equal_approx(curve->interpolate_baked(0.1), (real_t)0.4), "Custom linear curve should return the expected baked value at offset 0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate_baked(0.4), 0.4), + Math::is_equal_approx(curve->interpolate_baked(0.4), (real_t)0.4), "Custom linear curve should return the expected baked value at offset 0.4."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate_baked(0.7), 0.8), + Math::is_equal_approx(curve->interpolate_baked(0.7), (real_t)0.8), "Custom linear curve should return the expected baked value at offset 0.7."); CHECK_MESSAGE( Math::is_equal_approx(curve->interpolate_baked(1), 1), @@ -210,10 +210,10 @@ TEST_CASE("[Curve] Custom curve with linear tangents") { curve->remove_point(10); ERR_PRINT_ON; CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate(0.7), 0.8), + Math::is_equal_approx(curve->interpolate(0.7), (real_t)0.8), "Custom free curve should return the expected value at offset 0.7 after removing point at invalid index 10."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate_baked(0.7), 0.8), + Math::is_equal_approx(curve->interpolate_baked(0.7), (real_t)0.8), "Custom free curve should return the expected baked value at offset 0.7 after removing point at invalid index 10."); } } // namespace TestCurve diff --git a/tests/test_expression.h b/tests/test_expression.h index 0ef60d1a19..cb1d29389f 100644 --- a/tests/test_expression.h +++ b/tests/test_expression.h @@ -83,42 +83,42 @@ TEST_CASE("[Expression] Floating-point arithmetic") { expression.parse("-123.456") == OK, "Float identity should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), -123.456), + Math::is_equal_approx(double(expression.execute()), -123.456), "Float identity should return the expected result."); CHECK_MESSAGE( expression.parse("2.0 + 3.0") == OK, "Float addition should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 5), + Math::is_equal_approx(double(expression.execute()), 5), "Float addition should return the expected result."); CHECK_MESSAGE( expression.parse("3.0 / 10") == OK, "Float / integer division should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 0.3), + Math::is_equal_approx(double(expression.execute()), 0.3), "Float / integer division should return the expected result."); CHECK_MESSAGE( expression.parse("3 / 10.0") == OK, "Basic integer / float division should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 0.3), + Math::is_equal_approx(double(expression.execute()), 0.3), "Basic integer / float division should return the expected result."); CHECK_MESSAGE( expression.parse("3.0 / 10.0") == OK, "Float / float division should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 0.3), + Math::is_equal_approx(double(expression.execute()), 0.3), "Float / float division should return the expected result."); CHECK_MESSAGE( expression.parse("2.5 * (6.0 + 14.25) / 2.0 - 5.12345") == OK, "Float multiplication-addition-subtraction-division should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 20.18905), + Math::is_equal_approx(double(expression.execute()), 20.18905), "Float multiplication-addition-subtraction-division should return the expected result."); } @@ -129,7 +129,7 @@ TEST_CASE("[Expression] Scientific notation") { expression.parse("2.e5") == OK, "The expression should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 200'000), + Math::is_equal_approx(double(expression.execute()), 200'000), "The expression should return the expected result."); // The middle "e" is ignored here. @@ -137,14 +137,14 @@ TEST_CASE("[Expression] Scientific notation") { expression.parse("2e5") == OK, "The expression should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 25), + Math::is_equal_approx(double(expression.execute()), 25), "The expression should return the expected result."); CHECK_MESSAGE( expression.parse("2e.5") == OK, "The expression should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 2), + Math::is_equal_approx(double(expression.execute()), 2), "The expression should return the expected result."); } @@ -176,14 +176,14 @@ TEST_CASE("[Expression] Built-in functions") { expression.parse("snapped(sin(0.5), 0.01)") == OK, "The expression should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 0.48), + Math::is_equal_approx(double(expression.execute()), 0.48), "`snapped(sin(0.5), 0.01)` should return the expected result."); CHECK_MESSAGE( expression.parse("pow(2.0, -2500)") == OK, "The expression should parse successfully."); CHECK_MESSAGE( - Math::is_zero_approx(float(expression.execute())), + Math::is_zero_approx(double(expression.execute())), "`pow(2.0, -2500)` should return the expected result (asymptotically zero)."); } @@ -410,7 +410,7 @@ TEST_CASE("[Expression] Unusual expressions") { "The expression should parse successfully."); ERR_PRINT_OFF; CHECK_MESSAGE( - Math::is_inf(float(expression.execute())), + Math::is_inf(double(expression.execute())), "`-25.4 / 0` should return inf."); ERR_PRINT_ON; diff --git a/tests/test_json.h b/tests/test_json.h index e652a8fced..f1cb4799dc 100644 --- a/tests/test_json.h +++ b/tests/test_json.h @@ -80,7 +80,7 @@ TEST_CASE("[JSON] Parsing single data types") { err_line == 0, "Parsing an integer number as JSON should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(result, 123'456), + (int)result == 123'456, "Parsing an integer number as JSON should return the expected value."); json.parse("0.123456", result, err_str, err_line); @@ -155,7 +155,7 @@ TEST_CASE("[JSON] Parsing objects (dictionaries)") { dictionary["bugs"] == Variant(), "The parsed JSON should contain the expected values."); CHECK_MESSAGE( - Math::is_equal_approx(Dictionary(dictionary["apples"])["blue"], -20), + (int)Dictionary(dictionary["apples"])["blue"] == -20, "The parsed JSON should contain the expected values."); CHECK_MESSAGE( dictionary["empty_object"].hash() == Dictionary().hash(), diff --git a/tests/test_rect2.h b/tests/test_rect2.h index 5d58428fc0..821aa69970 100644 --- a/tests/test_rect2.h +++ b/tests/test_rect2.h @@ -312,19 +312,19 @@ TEST_CASE("[Rect2i] Basic setters") { TEST_CASE("[Rect2i] Area getters") { CHECK_MESSAGE( - Math::is_equal_approx(Rect2i(0, 100, 1280, 720).get_area(), 921'600), + Rect2i(0, 100, 1280, 720).get_area() == 921'600, "get_area() should return the expected value."); CHECK_MESSAGE( - Math::is_equal_approx(Rect2i(0, 100, -1280, -720).get_area(), 921'600), + Rect2i(0, 100, -1280, -720).get_area() == 921'600, "get_area() should return the expected value."); CHECK_MESSAGE( - Math::is_equal_approx(Rect2i(0, 100, 1280, -720).get_area(), -921'600), + Rect2i(0, 100, 1280, -720).get_area() == -921'600, "get_area() should return the expected value."); CHECK_MESSAGE( - Math::is_equal_approx(Rect2i(0, 100, -1280, 720).get_area(), -921'600), + Rect2i(0, 100, -1280, 720).get_area() == -921'600, "get_area() should return the expected value."); CHECK_MESSAGE( - Math::is_zero_approx(Rect2i(0, 100, 0, 720).get_area()), + Rect2i(0, 100, 0, 720).get_area() == 0, "get_area() should return the expected value."); CHECK_MESSAGE( |