summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--core/math/bvh.h695
-rw-r--r--core/math/bvh_abb.h276
-rw-r--r--core/math/bvh_cull.inc534
-rw-r--r--core/math/bvh_debug.inc68
-rw-r--r--core/math/bvh_integrity.inc42
-rw-r--r--core/math/bvh_logic.inc230
-rw-r--r--core/math/bvh_misc.inc55
-rw-r--r--core/math/bvh_pair.inc62
-rw-r--r--core/math/bvh_public.inc421
-rw-r--r--core/math/bvh_refit.inc141
-rw-r--r--core/math/bvh_split.inc294
-rw-r--r--core/math/bvh_structs.inc181
-rw-r--r--core/math/bvh_tree.h422
-rw-r--r--core/math/geometry_2d.h39
-rw-r--r--core/math/rect2.h12
-rw-r--r--core/math/vector2.h40
-rw-r--r--core/math/vector3.cpp8
-rw-r--r--core/math/vector3.h15
-rw-r--r--core/templates/pooled_list.h (renamed from servers/physics_3d/broad_phase_3d_basic.h)124
-rw-r--r--core/variant/callable.h7
-rw-r--r--doc/classes/AudioEffectPitchShift.xml16
-rw-r--r--doc/classes/AudioEffectSpectrumAnalyzer.xml16
-rw-r--r--doc/classes/DisplayServer.xml60
-rw-r--r--doc/classes/OS.xml1
-rw-r--r--doc/classes/ProjectSettings.xml9
-rw-r--r--doc/classes/Skeleton3D.xml9
-rw-r--r--doc/classes/TileData.xml245
-rw-r--r--doc/classes/TileMap.xml281
-rw-r--r--doc/classes/TileSet.xml708
-rw-r--r--doc/classes/TileSetAtlasSource.xml207
-rw-r--r--doc/classes/TileSetSource.xml13
-rw-r--r--drivers/dummy/rasterizer_dummy.h9
-rw-r--r--drivers/vulkan/vulkan_context.cpp14
-rw-r--r--editor/editor_node.cpp75
-rw-r--r--editor/editor_node.h1
-rw-r--r--editor/editor_properties.cpp4
-rw-r--r--editor/editor_settings.cpp4
-rw-r--r--editor/editor_themes.cpp41
-rw-r--r--editor/editor_zoom_widget.cpp163
-rw-r--r--editor/editor_zoom_widget.h (renamed from platform/iphone/native_video_view.h)39
-rw-r--r--editor/icons/AddAtlasTile.svg1
-rw-r--r--editor/icons/AddAutotile.svg1
-rw-r--r--editor/icons/AddSingleTile.svg1
-rw-r--r--editor/icons/CollapseTree.svg1
-rw-r--r--editor/icons/EditAddRemove.svg1
-rw-r--r--editor/icons/EditorHandleDisabled.svg1
-rw-r--r--editor/icons/Eraser.svg1
-rw-r--r--editor/icons/ExpandTree.svg1
-rw-r--r--editor/icons/FontSize.svg2
-rw-r--r--editor/icons/RectangleAddRemove.svg1
-rw-r--r--editor/icons/TerrainMatchCorners.svg1
-rw-r--r--editor/icons/TerrainMatchCornersAndSides.svg1
-rw-r--r--editor/icons/TerrainMatchSides.svg1
-rw-r--r--editor/icons/ThemeDeselectAll.svg1
-rw-r--r--editor/icons/ThemeRemoveAllItems.svg2
-rw-r--r--editor/icons/ThemeRemoveCustomItems.svg2
-rw-r--r--editor/icons/ThemeSelectAll.svg1
-rw-r--r--editor/icons/ThemeSelectFull.svg1
-rw-r--r--editor/icons/WarningPattern.svg1
-rw-r--r--editor/plugins/SCsub2
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp125
-rw-r--r--editor/plugins/canvas_item_editor_plugin.h13
-rw-r--r--editor/plugins/script_editor_plugin.cpp112
-rw-r--r--editor/plugins/script_editor_plugin.h2
-rw-r--r--editor/plugins/theme_editor_plugin.cpp1701
-rw-r--r--editor/plugins/theme_editor_plugin.h174
-rw-r--r--editor/plugins/tile_map_editor_plugin.cpp2335
-rw-r--r--editor/plugins/tile_map_editor_plugin.h242
-rw-r--r--editor/plugins/tile_set_editor_plugin.cpp3680
-rw-r--r--editor/plugins/tile_set_editor_plugin.h298
-rw-r--r--editor/plugins/tiles/SCsub5
-rw-r--r--editor/plugins/tiles/tile_atlas_view.cpp649
-rw-r--r--editor/plugins/tiles/tile_atlas_view.h153
-rw-r--r--editor/plugins/tiles/tile_data_editors.cpp218
-rw-r--r--editor/plugins/tiles/tile_data_editors.h110
-rw-r--r--editor/plugins/tiles/tile_map_editor.cpp3368
-rw-r--r--editor/plugins/tiles/tile_map_editor.h328
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.cpp1858
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.h260
-rw-r--r--editor/plugins/tiles/tile_set_editor.cpp520
-rw-r--r--editor/plugins/tiles/tile_set_editor.h94
-rw-r--r--editor/plugins/tiles/tiles_editor_plugin.cpp276
-rw-r--r--editor/plugins/tiles/tiles_editor_plugin.h114
-rw-r--r--icon_outlined.pngbin0 -> 9282 bytes
-rw-r--r--icon_outlined.svg159
-rw-r--r--logo_outlined.pngbin0 -> 17898 bytes
-rw-r--r--logo_outlined.svg185
-rwxr-xr-xmisc/hooks/pre-commit-clang-format8
-rw-r--r--modules/bmp/image_loader_bmp.cpp26
-rw-r--r--modules/gdnative/include/gdnative/callable.h1
-rw-r--r--modules/gdnative/include/gdnative/signal.h1
-rw-r--r--modules/raycast/config.py8
-rw-r--r--modules/raycast/lightmap_raycaster.cpp6
-rw-r--r--platform/android/display_server_android.cpp1
-rw-r--r--platform/iphone/SCsub1
-rw-r--r--platform/iphone/display_server_iphone.h6
-rw-r--r--platform/iphone/display_server_iphone.mm65
-rw-r--r--platform/iphone/native_video_view.m266
-rw-r--r--platform/iphone/os_iphone.mm8
-rw-r--r--platform/iphone/view_controller.h5
-rw-r--r--platform/iphone/view_controller.mm24
-rw-r--r--platform/javascript/display_server_javascript.cpp1
-rw-r--r--platform/javascript/javascript_main.cpp5
-rw-r--r--platform/javascript/package-lock.json39
-rw-r--r--platform/javascript/package.json2
-rw-r--r--platform/linuxbsd/display_server_x11.cpp3
-rw-r--r--platform/uwp/SCsub1
-rw-r--r--scene/2d/gpu_particles_2d.cpp1
-rw-r--r--scene/2d/polygon_2d.cpp48
-rw-r--r--scene/2d/polygon_2d.h3
-rw-r--r--scene/2d/tile_map.cpp2660
-rw-r--r--scene/2d/tile_map.h413
-rw-r--r--scene/3d/collision_object_3d.cpp125
-rw-r--r--scene/3d/collision_object_3d.h15
-rw-r--r--scene/3d/collision_shape_3d.cpp14
-rw-r--r--scene/3d/collision_shape_3d.h2
-rw-r--r--scene/3d/gpu_particles_3d.cpp1
-rw-r--r--scene/3d/physics_body_3d.cpp2
-rw-r--r--scene/3d/ray_cast_3d.cpp2
-rw-r--r--scene/3d/skeleton_3d.cpp87
-rw-r--r--scene/3d/skeleton_3d.h2
-rw-r--r--scene/3d/skeleton_ik_3d.cpp69
-rw-r--r--scene/animation/animation_tree.cpp4
-rw-r--r--scene/gui/color_picker.cpp18
-rw-r--r--scene/gui/color_picker.h1
-rw-r--r--scene/gui/control.cpp36
-rw-r--r--scene/gui/item_list.cpp3
-rw-r--r--scene/gui/line_edit.cpp2
-rw-r--r--scene/gui/text_edit.cpp2
-rw-r--r--scene/gui/tree.cpp15
-rw-r--r--scene/register_scene_types.cpp3
-rw-r--r--scene/resources/tile_set.cpp4797
-rw-r--r--scene/resources/tile_set.h686
-rw-r--r--servers/audio/effects/audio_effect_pitch_shift.cpp4
-rw-r--r--servers/audio/effects/audio_effect_pitch_shift.h10
-rw-r--r--servers/audio/effects/audio_effect_spectrum_analyzer.cpp4
-rw-r--r--servers/audio/effects/audio_effect_spectrum_analyzer.h10
-rw-r--r--servers/display_server.cpp28
-rw-r--r--servers/display_server.h8
-rw-r--r--servers/physics_2d/broad_phase_2d_basic.cpp174
-rw-r--r--servers/physics_2d/broad_phase_2d_bvh.cpp116
-rw-r--r--servers/physics_2d/broad_phase_2d_bvh.h (renamed from servers/physics_2d/broad_phase_2d_basic.h)58
-rw-r--r--servers/physics_2d/broad_phase_2d_hash_grid.cpp738
-rw-r--r--servers/physics_2d/broad_phase_2d_hash_grid.h194
-rw-r--r--servers/physics_2d/broad_phase_2d_sw.h2
-rw-r--r--servers/physics_2d/collision_object_2d_sw.cpp24
-rw-r--r--servers/physics_2d/physics_server_2d_sw.cpp6
-rw-r--r--servers/physics_3d/broad_phase_3d_basic.cpp212
-rw-r--r--servers/physics_3d/broad_phase_3d_bvh.cpp (renamed from servers/physics_3d/broad_phase_octree.cpp)74
-rw-r--r--servers/physics_3d/broad_phase_3d_bvh.h (renamed from servers/physics_3d/broad_phase_octree.h)22
-rw-r--r--servers/physics_3d/broad_phase_3d_sw.h2
-rw-r--r--servers/physics_3d/collision_object_3d_sw.cpp24
-rw-r--r--servers/physics_3d/physics_server_3d_sw.cpp6
-rw-r--r--servers/rendering/renderer_canvas_cull.cpp29
-rw-r--r--servers/rendering/renderer_canvas_render.h7
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp387
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h8
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp273
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h8
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp368
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.h10
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp2
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_gi_rd.h5
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_render_rd.cpp149
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_render_rd.h74
-rw-r--r--servers/rendering/renderer_rd/renderer_storage_rd.cpp23
-rw-r--r--servers/rendering/renderer_rd/renderer_storage_rd.h25
-rw-r--r--servers/rendering/renderer_rd/shaders/canvas.glsl102
-rw-r--r--servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl10
-rw-r--r--servers/rendering/renderer_rd/shaders/particles_copy.glsl9
-rw-r--r--servers/rendering/renderer_rd/shaders/skeleton.glsl47
-rw-r--r--servers/rendering/renderer_storage.h1
-rw-r--r--servers/rendering/rendering_server_default.h1
-rw-r--r--servers/rendering_server.cpp2
-rw-r--r--servers/rendering_server.h6
176 files changed, 21669 insertions, 13667 deletions
diff --git a/README.md b/README.md
index 72a85492fd..8ddddf0e63 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
<p align="center">
<a href="https://godotengine.org">
- <img src="logo.svg" width="400" alt="Godot Engine logo">
+ <img src="logo_outlined.svg" width="400" alt="Godot Engine logo">
</a>
</p>
diff --git a/core/math/bvh.h b/core/math/bvh.h
new file mode 100644
index 0000000000..cefbc9b0db
--- /dev/null
+++ b/core/math/bvh.h
@@ -0,0 +1,695 @@
+/*************************************************************************/
+/* bvh.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 BVH_H
+#define BVH_H
+
+// BVH
+// This class provides a wrapper around BVH tree, which contains most of the functionality
+// for a dynamic BVH with templated leaf size.
+// However BVH also adds facilities for pairing, to maintain compatibility with Godot 3.2.
+// Pairing is a collision pairing system, on top of the basic BVH.
+
+// Some notes on the use of BVH / Octree from Godot 3.2.
+// This is not well explained elsewhere.
+// The rendering tree mask and types that are sent to the BVH are NOT layer masks.
+// They are INSTANCE_TYPES (defined in visual_server.h), e.g. MESH, MULTIMESH, PARTICLES etc.
+// Thus the lights do no cull by layer mask in the BVH.
+
+// Layer masks are implemented in the renderers as a later step, and light_cull_mask appears to be
+// implemented in GLES3 but not GLES2. Layer masks are not yet implemented for directional lights.
+
+#include "bvh_tree.h"
+
+#define BVHTREE_CLASS BVH_Tree<T, 2, MAX_ITEMS, USE_PAIRS, Bounds, Point>
+
+template <class T, bool USE_PAIRS = false, int MAX_ITEMS = 32, class Bounds = AABB, class Point = Vector3>
+class BVH_Manager {
+public:
+ // note we are using uint32_t instead of BVHHandle, losing type safety, but this
+ // is for compatibility with octree
+ typedef void *(*PairCallback)(void *, uint32_t, T *, int, uint32_t, T *, int);
+ typedef void (*UnpairCallback)(void *, uint32_t, T *, int, uint32_t, T *, int, void *);
+
+ // these 2 are crucial for fine tuning, and can be applied manually
+ // see the variable declarations for more info.
+ void params_set_node_expansion(real_t p_value) {
+ if (p_value >= 0.0) {
+ tree._node_expansion = p_value;
+ tree._auto_node_expansion = false;
+ } else {
+ tree._auto_node_expansion = true;
+ }
+ }
+
+ void params_set_pairing_expansion(real_t p_value) {
+ if (p_value >= 0.0) {
+ tree._pairing_expansion = p_value;
+ tree._auto_pairing_expansion = false;
+ } else {
+ tree._auto_pairing_expansion = true;
+ }
+ }
+
+ void set_pair_callback(PairCallback p_callback, void *p_userdata) {
+ pair_callback = p_callback;
+ pair_callback_userdata = p_userdata;
+ }
+ void set_unpair_callback(UnpairCallback p_callback, void *p_userdata) {
+ unpair_callback = p_callback;
+ unpair_callback_userdata = p_userdata;
+ }
+
+ BVHHandle create(T *p_userdata, bool p_active, const Bounds &p_aabb = Bounds(), int p_subindex = 0, bool p_pairable = false, uint32_t p_pairable_type = 0, uint32_t p_pairable_mask = 1) {
+ // not sure if absolutely necessary to flush collisions here. It will cost performance to, instead
+ // of waiting for update, so only uncomment this if there are bugs.
+ if (USE_PAIRS) {
+ //_check_for_collisions();
+ }
+
+#ifdef TOOLS_ENABLED
+ if (!USE_PAIRS) {
+ if (p_pairable) {
+ WARN_PRINT_ONCE("creating pairable item in BVH with USE_PAIRS set to false");
+ }
+ }
+#endif
+
+ BVHHandle h = tree.item_add(p_userdata, p_active, p_aabb, p_subindex, p_pairable, p_pairable_type, p_pairable_mask);
+
+ if (USE_PAIRS) {
+ // for safety initialize the expanded AABB
+ Bounds &expanded_aabb = tree._pairs[h.id()].expanded_aabb;
+ expanded_aabb = p_aabb;
+ expanded_aabb.grow_by(tree._pairing_expansion);
+
+ // force a collision check no matter the AABB
+ if (p_active) {
+ _add_changed_item(h, p_aabb, false);
+ _check_for_collisions(true);
+ }
+ }
+
+ return h;
+ }
+
+ ////////////////////////////////////////////////////
+ // wrapper versions that use uint32_t instead of handle
+ // for backward compatibility. Less type safe
+ void move(uint32_t p_handle, const Bounds &p_aabb) {
+ BVHHandle h;
+ h.set(p_handle);
+ move(h, p_aabb);
+ }
+
+ void erase(uint32_t p_handle) {
+ BVHHandle h;
+ h.set(p_handle);
+ erase(h);
+ }
+
+ void force_collision_check(uint32_t p_handle) {
+ BVHHandle h;
+ h.set(p_handle);
+ force_collision_check(h);
+ }
+
+ bool activate(uint32_t p_handle, const Bounds &p_aabb, bool p_delay_collision_check = false) {
+ BVHHandle h;
+ h.set(p_handle);
+ return activate(h, p_aabb, p_delay_collision_check);
+ }
+
+ bool deactivate(uint32_t p_handle) {
+ BVHHandle h;
+ h.set(p_handle);
+ return deactivate(h);
+ }
+
+ void set_pairable(uint32_t p_handle, bool p_pairable, uint32_t p_pairable_type, uint32_t p_pairable_mask, bool p_force_collision_check = true) {
+ BVHHandle h;
+ h.set(p_handle);
+ set_pairable(h, p_pairable, p_pairable_type, p_pairable_mask, p_force_collision_check);
+ }
+
+ bool is_pairable(uint32_t p_handle) const {
+ BVHHandle h;
+ h.set(p_handle);
+ return item_is_pairable(h);
+ }
+ int get_subindex(uint32_t p_handle) const {
+ BVHHandle h;
+ h.set(p_handle);
+ return item_get_subindex(h);
+ }
+
+ T *get(uint32_t p_handle) const {
+ BVHHandle h;
+ h.set(p_handle);
+ return item_get_userdata(h);
+ }
+
+ ////////////////////////////////////////////////////
+
+ void move(BVHHandle p_handle, const Bounds &p_aabb) {
+ if (tree.item_move(p_handle, p_aabb)) {
+ if (USE_PAIRS) {
+ _add_changed_item(p_handle, p_aabb);
+ }
+ }
+ }
+
+ void erase(BVHHandle p_handle) {
+ // call unpair and remove all references to the item
+ // before deleting from the tree
+ if (USE_PAIRS) {
+ _remove_changed_item(p_handle);
+ }
+
+ tree.item_remove(p_handle);
+
+ _check_for_collisions(true);
+ }
+
+ // use in conjunction with activate if you have deferred the collision check, and
+ // set pairable has never been called.
+ // (deferred collision checks are a workaround for visual server for historical reasons)
+ void force_collision_check(BVHHandle p_handle) {
+ if (USE_PAIRS) {
+ // the aabb should already be up to date in the BVH
+ Bounds aabb;
+ item_get_AABB(p_handle, aabb);
+
+ // add it as changed even if aabb not different
+ _add_changed_item(p_handle, aabb, false);
+
+ // force an immediate full collision check, much like calls to set_pairable
+ _check_for_collisions(true);
+ }
+ }
+
+ // these should be read as set_visible for render trees,
+ // but generically this makes items add or remove from the
+ // tree internally, to speed things up by ignoring inactive items
+ bool activate(BVHHandle p_handle, const Bounds &p_aabb, bool p_delay_collision_check = false) {
+ // sending the aabb here prevents the need for the BVH to maintain
+ // a redundant copy of the aabb.
+ // returns success
+ if (tree.item_activate(p_handle, p_aabb)) {
+ if (USE_PAIRS) {
+ // in the special case of the render tree, when setting visibility we are using the combination of
+ // activate then set_pairable. This would case 2 sets of collision checks. For efficiency here we allow
+ // deferring to have a single collision check at the set_pairable call.
+ // Watch for bugs! This may cause bugs if set_pairable is not called.
+ if (!p_delay_collision_check) {
+ _add_changed_item(p_handle, p_aabb, false);
+
+ // force an immediate collision check, much like calls to set_pairable
+ _check_for_collisions(true);
+ }
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ bool deactivate(BVHHandle p_handle) {
+ // returns success
+ if (tree.item_deactivate(p_handle)) {
+ // call unpair and remove all references to the item
+ // before deleting from the tree
+ if (USE_PAIRS) {
+ _remove_changed_item(p_handle);
+
+ // force check for collisions, much like an erase was called
+ _check_for_collisions(true);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ bool get_active(BVHHandle p_handle) const {
+ return tree.item_get_active(p_handle);
+ }
+
+ // call e.g. once per frame (this does a trickle optimize)
+ void update() {
+ tree.update();
+ _check_for_collisions();
+#ifdef BVH_INTEGRITY_CHECKS
+ tree.integrity_check_all();
+#endif
+ }
+
+ // this can be called more frequently than per frame if necessary
+ void update_collisions() {
+ _check_for_collisions();
+ }
+
+ // prefer calling this directly as type safe
+ void set_pairable(const BVHHandle &p_handle, bool p_pairable, uint32_t p_pairable_type, uint32_t p_pairable_mask, bool p_force_collision_check = true) {
+ // Returns true if the pairing state has changed.
+ bool state_changed = tree.item_set_pairable(p_handle, p_pairable, p_pairable_type, p_pairable_mask);
+
+ if (USE_PAIRS) {
+ // not sure if absolutely necessary to flush collisions here. It will cost performance to, instead
+ // of waiting for update, so only uncomment this if there are bugs.
+ //_check_for_collisions();
+
+ if ((p_force_collision_check || state_changed) && get_active(p_handle)) {
+ // when the pairable state changes, we need to force a collision check because newly pairable
+ // items may be in collision, and unpairable items might move out of collision.
+ // We cannot depend on waiting for the next update, because that may come much later.
+ Bounds aabb;
+ item_get_AABB(p_handle, aabb);
+
+ // passing false disables the optimization which prevents collision checks if
+ // the aabb hasn't changed
+ _add_changed_item(p_handle, aabb, false);
+
+ // force an immediate collision check (probably just for this one item)
+ // but it must be a FULL collision check, also checking pairable state and masks.
+ // This is because AABB intersecting objects may have changed pairable state / mask
+ // such that they should no longer be paired. E.g. lights.
+ _check_for_collisions(true);
+ } // only if active
+ }
+ }
+
+ // cull tests
+ int cull_aabb(const Bounds &p_aabb, T **p_result_array, int p_result_max, int *p_subindex_array = nullptr, uint32_t p_mask = 0xFFFFFFFF) {
+ typename BVHTREE_CLASS::CullParams params;
+
+ params.result_count_overall = 0;
+ params.result_max = p_result_max;
+ params.result_array = p_result_array;
+ params.subindex_array = p_subindex_array;
+ params.mask = p_mask;
+ params.pairable_type = 0;
+ params.test_pairable_only = false;
+ params.abb.from(p_aabb);
+
+ tree.cull_aabb(params);
+
+ return params.result_count_overall;
+ }
+
+ int cull_segment(const Point &p_from, const Point &p_to, T **p_result_array, int p_result_max, int *p_subindex_array = nullptr, uint32_t p_mask = 0xFFFFFFFF) {
+ typename BVHTREE_CLASS::CullParams params;
+
+ params.result_count_overall = 0;
+ params.result_max = p_result_max;
+ params.result_array = p_result_array;
+ params.subindex_array = p_subindex_array;
+ params.mask = p_mask;
+ params.pairable_type = 0;
+
+ params.segment.from = p_from;
+ params.segment.to = p_to;
+
+ tree.cull_segment(params);
+
+ return params.result_count_overall;
+ }
+
+ int cull_point(const Point &p_point, T **p_result_array, int p_result_max, int *p_subindex_array = nullptr, uint32_t p_mask = 0xFFFFFFFF) {
+ typename BVHTREE_CLASS::CullParams params;
+
+ params.result_count_overall = 0;
+ params.result_max = p_result_max;
+ params.result_array = p_result_array;
+ params.subindex_array = p_subindex_array;
+ params.mask = p_mask;
+ params.pairable_type = 0;
+
+ params.point = p_point;
+
+ tree.cull_point(params);
+ return params.result_count_overall;
+ }
+
+ int cull_convex(const Vector<Plane> &p_convex, T **p_result_array, int p_result_max, uint32_t p_mask = 0xFFFFFFFF) {
+ if (!p_convex.size()) {
+ return 0;
+ }
+
+ Vector<Vector3> convex_points = Geometry3D::compute_convex_mesh_points(&p_convex[0], p_convex.size());
+ if (convex_points.size() == 0) {
+ return 0;
+ }
+
+ typename BVHTREE_CLASS::CullParams params;
+ params.result_count_overall = 0;
+ params.result_max = p_result_max;
+ params.result_array = p_result_array;
+ params.subindex_array = nullptr;
+ params.mask = p_mask;
+ params.pairable_type = 0;
+
+ params.hull.planes = &p_convex[0];
+ params.hull.num_planes = p_convex.size();
+ params.hull.points = &convex_points[0];
+ params.hull.num_points = convex_points.size();
+
+ tree.cull_convex(params);
+
+ return params.result_count_overall;
+ }
+
+private:
+ // do this after moving etc.
+ void _check_for_collisions(bool p_full_check = false) {
+ if (!changed_items.size()) {
+ // noop
+ return;
+ }
+
+ Bounds bb;
+
+ typename BVHTREE_CLASS::CullParams params;
+
+ params.result_count_overall = 0;
+ params.result_max = INT_MAX;
+ params.result_array = nullptr;
+ params.subindex_array = nullptr;
+ params.mask = 0xFFFFFFFF;
+ params.pairable_type = 0;
+
+ for (unsigned int n = 0; n < changed_items.size(); n++) {
+ const BVHHandle &h = changed_items[n];
+
+ // use the expanded aabb for pairing
+ const Bounds &expanded_aabb = tree._pairs[h.id()].expanded_aabb;
+ BVHABB_CLASS abb;
+ abb.from(expanded_aabb);
+
+ // find all the existing paired aabbs that are no longer
+ // paired, and send callbacks
+ _find_leavers(h, abb, p_full_check);
+
+ uint32_t changed_item_ref_id = h.id();
+
+ // set up the test from this item.
+ // this includes whether to test the non pairable tree,
+ // and the item mask.
+ tree.item_fill_cullparams(h, params);
+
+ params.abb = abb;
+
+ params.result_count_overall = 0; // might not be needed
+ tree.cull_aabb(params, false);
+
+ for (unsigned int i = 0; i < tree._cull_hits.size(); i++) {
+ uint32_t ref_id = tree._cull_hits[i];
+
+ // don't collide against ourself
+ if (ref_id == changed_item_ref_id) {
+ continue;
+ }
+
+#ifdef BVH_CHECKS
+ // if neither are pairable, they should ignore each other
+ // THIS SHOULD NEVER HAPPEN .. now we only test the pairable tree
+ // if the changed item is not pairable
+ CRASH_COND(params.test_pairable_only && !tree._extra[ref_id].pairable);
+#endif
+
+ // checkmasks is already done in the cull routine.
+ BVHHandle h_collidee;
+ h_collidee.set_id(ref_id);
+
+ // find NEW enterers, and send callbacks for them only
+ _collide(h, h_collidee);
+ }
+ }
+ _reset();
+ }
+
+public:
+ void item_get_AABB(BVHHandle p_handle, Bounds &r_aabb) {
+ BVHABB_CLASS abb;
+ tree.item_get_ABB(p_handle, abb);
+ abb.to(r_aabb);
+ }
+
+private:
+ // supplemental funcs
+ bool item_is_pairable(BVHHandle p_handle) const { return _get_extra(p_handle).pairable; }
+ T *item_get_userdata(BVHHandle p_handle) const { return _get_extra(p_handle).userdata; }
+ int item_get_subindex(BVHHandle p_handle) const { return _get_extra(p_handle).subindex; }
+
+ void _unpair(BVHHandle p_from, BVHHandle p_to) {
+ tree._handle_sort(p_from, p_to);
+
+ typename BVHTREE_CLASS::ItemExtra &exa = tree._extra[p_from.id()];
+ typename BVHTREE_CLASS::ItemExtra &exb = tree._extra[p_to.id()];
+
+ // if the userdata is the same, no collisions should occur
+ if ((exa.userdata == exb.userdata) && exa.userdata) {
+ return;
+ }
+
+ typename BVHTREE_CLASS::ItemPairs &pairs_from = tree._pairs[p_from.id()];
+ typename BVHTREE_CLASS::ItemPairs &pairs_to = tree._pairs[p_to.id()];
+
+ void *ud_from = pairs_from.remove_pair_to(p_to);
+ pairs_to.remove_pair_to(p_from);
+
+ // callback
+ if (unpair_callback) {
+ unpair_callback(pair_callback_userdata, p_from, exa.userdata, exa.subindex, p_to, exb.userdata, exb.subindex, ud_from);
+ }
+ }
+
+ // returns true if unpair
+ bool _find_leavers_process_pair(typename BVHTREE_CLASS::ItemPairs &p_pairs_from, const BVHABB_CLASS &p_abb_from, BVHHandle p_from, BVHHandle p_to, bool p_full_check) {
+ BVHABB_CLASS abb_to;
+ tree.item_get_ABB(p_to, abb_to);
+
+ // do they overlap?
+ if (p_abb_from.intersects(abb_to)) {
+ // the full check for pairable / non pairable and mask changes is extra expense
+ // this need not be done in most cases (for speed) except in the case where set_pairable is called
+ // where the masks etc of the objects in question may have changed
+ if (!p_full_check) {
+ return false;
+ }
+ const typename BVHTREE_CLASS::ItemExtra &exa = _get_extra(p_from);
+ const typename BVHTREE_CLASS::ItemExtra &exb = _get_extra(p_to);
+
+ // one of the two must be pairable to still pair
+ // if neither are pairable, we always unpair
+ if (exa.pairable || exb.pairable) {
+ // the masks must still be compatible to pair
+ // i.e. if there is a hit between the two, then they should stay paired
+ if (tree._cull_pairing_mask_test_hit(exa.pairable_mask, exa.pairable_type, exb.pairable_mask, exb.pairable_type)) {
+ return false;
+ }
+ }
+ }
+
+ _unpair(p_from, p_to);
+ return true;
+ }
+
+ // find all the existing paired aabbs that are no longer
+ // paired, and send callbacks
+ void _find_leavers(BVHHandle p_handle, const BVHABB_CLASS &expanded_abb_from, bool p_full_check) {
+ typename BVHTREE_CLASS::ItemPairs &p_from = tree._pairs[p_handle.id()];
+
+ BVHABB_CLASS abb_from = expanded_abb_from;
+
+ // remove from pairing list for every partner
+ for (unsigned int n = 0; n < p_from.extended_pairs.size(); n++) {
+ BVHHandle h_to = p_from.extended_pairs[n].handle;
+ if (_find_leavers_process_pair(p_from, abb_from, p_handle, h_to, p_full_check)) {
+ // we need to keep the counter n up to date if we deleted a pair
+ // as the number of items in p_from.extended_pairs will have decreased by 1
+ // and we don't want to miss an item
+ n--;
+ }
+ }
+ }
+
+ // find NEW enterers, and send callbacks for them only
+ // handle a and b
+ void _collide(BVHHandle p_ha, BVHHandle p_hb) {
+ // only have to do this oneway, lower ID then higher ID
+ tree._handle_sort(p_ha, p_hb);
+
+ const typename BVHTREE_CLASS::ItemExtra &exa = _get_extra(p_ha);
+ const typename BVHTREE_CLASS::ItemExtra &exb = _get_extra(p_hb);
+
+ // if the userdata is the same, no collisions should occur
+ if ((exa.userdata == exb.userdata) && exa.userdata) {
+ return;
+ }
+
+ typename BVHTREE_CLASS::ItemPairs &p_from = tree._pairs[p_ha.id()];
+ typename BVHTREE_CLASS::ItemPairs &p_to = tree._pairs[p_hb.id()];
+
+ // does this pair exist already?
+ // or only check the one with lower number of pairs for greater speed
+ if (p_from.num_pairs <= p_to.num_pairs) {
+ if (p_from.contains_pair_to(p_hb)) {
+ return;
+ }
+ } else {
+ if (p_to.contains_pair_to(p_ha)) {
+ return;
+ }
+ }
+
+ // callback
+ void *callback_userdata = nullptr;
+
+ if (pair_callback) {
+ callback_userdata = pair_callback(pair_callback_userdata, p_ha, exa.userdata, exa.subindex, p_hb, exb.userdata, exb.subindex);
+ }
+
+ // new pair! .. only really need to store the userdata on the lower handle, but both have storage so...
+ p_from.add_pair_to(p_hb, callback_userdata);
+ p_to.add_pair_to(p_ha, callback_userdata);
+ }
+
+ // if we remove an item, we need to immediately remove the pairs, to prevent reading the pair after deletion
+ void _remove_pairs_containing(BVHHandle p_handle) {
+ typename BVHTREE_CLASS::ItemPairs &p_from = tree._pairs[p_handle.id()];
+
+ // remove from pairing list for every partner.
+ // can't easily use a for loop here, because removing changes the size of the list
+ while (p_from.extended_pairs.size()) {
+ BVHHandle h_to = p_from.extended_pairs[0].handle;
+ _unpair(p_handle, h_to);
+ }
+ }
+
+private:
+ const typename BVHTREE_CLASS::ItemExtra &_get_extra(BVHHandle p_handle) const {
+ return tree._extra[p_handle.id()];
+ }
+ const typename BVHTREE_CLASS::ItemRef &_get_ref(BVHHandle p_handle) const {
+ return tree._refs[p_handle.id()];
+ }
+
+ void _reset() {
+ changed_items.clear();
+ _tick++;
+ }
+
+ void _add_changed_item(BVHHandle p_handle, const Bounds &aabb, bool p_check_aabb = true) {
+ // Note that non pairable items can pair with pairable,
+ // so all types must be added to the list
+
+ // aabb check with expanded aabb. This greatly decreases processing
+ // at the cost of slightly less accurate pairing checks
+ // Note this pairing AABB is separate from the AABB in the actual tree
+ Bounds &expanded_aabb = tree._pairs[p_handle.id()].expanded_aabb;
+
+ // passing p_check_aabb false disables the optimization which prevents collision checks if
+ // the aabb hasn't changed. This is needed where set_pairable has been called, but the position
+ // has not changed.
+ if (p_check_aabb && expanded_aabb.encloses(aabb)) {
+ return;
+ }
+
+ // ALWAYS update the new expanded aabb, even if already changed once
+ // this tick, because it is vital that the AABB is kept up to date
+ expanded_aabb = aabb;
+ expanded_aabb.grow_by(tree._pairing_expansion);
+
+ // this code is to ensure that changed items only appear once on the updated list
+ // collision checking them multiple times is not needed, and repeats the same thing
+ uint32_t &last_updated_tick = tree._extra[p_handle.id()].last_updated_tick;
+
+ if (last_updated_tick == _tick) {
+ return; // already on changed list
+ }
+
+ // mark as on list
+ last_updated_tick = _tick;
+
+ // add to the list
+ changed_items.push_back(p_handle);
+ }
+
+ void _remove_changed_item(BVHHandle p_handle) {
+ // Care has to be taken here for items that are deleted. The ref ID
+ // could be reused on the same tick for new items. This is probably
+ // rare but should be taken into consideration
+
+ // callbacks
+ _remove_pairs_containing(p_handle);
+
+ // remove from changed items (not very efficient yet)
+ for (int n = 0; n < (int)changed_items.size(); n++) {
+ if (changed_items[n] == p_handle) {
+ changed_items.remove_unordered(n);
+
+ // because we are using an unordered remove,
+ // the last changed item will now be at spot 'n',
+ // and we need to redo it, so we prevent moving on to
+ // the next n at the next for iteration.
+ n--;
+ }
+ }
+
+ // reset the last updated tick (may not be necessary but just in case)
+ tree._extra[p_handle.id()].last_updated_tick = 0;
+ }
+
+ PairCallback pair_callback;
+ UnpairCallback unpair_callback;
+ void *pair_callback_userdata;
+ void *unpair_callback_userdata;
+
+ BVHTREE_CLASS tree;
+
+ // for collision pairing,
+ // maintain a list of all items moved etc on each frame / tick
+ LocalVector<BVHHandle, uint32_t, true> changed_items;
+ uint32_t _tick;
+
+public:
+ BVH_Manager() {
+ _tick = 1; // start from 1 so items with 0 indicate never updated
+ pair_callback = nullptr;
+ unpair_callback = nullptr;
+ pair_callback_userdata = nullptr;
+ unpair_callback_userdata = nullptr;
+ }
+};
+
+#undef BVHTREE_CLASS
+
+#endif // BVH_H
diff --git a/core/math/bvh_abb.h b/core/math/bvh_abb.h
new file mode 100644
index 0000000000..bd9a01a87e
--- /dev/null
+++ b/core/math/bvh_abb.h
@@ -0,0 +1,276 @@
+/*************************************************************************/
+/* bvh_abb.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 BVH_ABB_H
+#define BVH_ABB_H
+
+// special optimized version of axis aligned bounding box
+template <class Bounds = AABB, class Point = Vector3>
+struct BVH_ABB {
+ struct ConvexHull {
+ // convex hulls (optional)
+ const Plane *planes;
+ int num_planes;
+ const Vector3 *points;
+ int num_points;
+ };
+
+ struct Segment {
+ Point from;
+ Point to;
+ };
+
+ enum IntersectResult {
+ IR_MISS = 0,
+ IR_PARTIAL,
+ IR_FULL,
+ };
+
+ // we store mins with a negative value in order to test them with SIMD
+ Point min;
+ Point neg_max;
+
+ bool operator==(const BVH_ABB &o) const { return (min == o.min) && (neg_max == o.neg_max); }
+ bool operator!=(const BVH_ABB &o) const { return (*this == o) == false; }
+
+ void set(const Point &_min, const Point &_max) {
+ min = _min;
+ neg_max = -_max;
+ }
+
+ // to and from standard AABB
+ void from(const Bounds &p_aabb) {
+ min = p_aabb.position;
+ neg_max = -(p_aabb.position + p_aabb.size);
+ }
+
+ void to(Bounds &r_aabb) const {
+ r_aabb.position = min;
+ r_aabb.size = calculate_size();
+ }
+
+ void merge(const BVH_ABB &p_o) {
+ for (int axis = 0; axis < Point::AXIS_COUNT; ++axis) {
+ neg_max[axis] = MIN(neg_max[axis], p_o.neg_max[axis]);
+ min[axis] = MIN(min[axis], p_o.min[axis]);
+ }
+ }
+
+ Point calculate_size() const {
+ return -neg_max - min;
+ }
+
+ Point calculate_centre() const {
+ return Point((calculate_size() * 0.5) + min);
+ }
+
+ real_t get_proximity_to(const BVH_ABB &p_b) const {
+ const Point d = (min - neg_max) - (p_b.min - p_b.neg_max);
+ real_t proximity = 0.0;
+ for (int axis = 0; axis < Point::AXIS_COUNT; ++axis) {
+ proximity += Math::abs(d[axis]);
+ }
+ return proximity;
+ }
+
+ int select_by_proximity(const BVH_ABB &p_a, const BVH_ABB &p_b) const {
+ return (get_proximity_to(p_a) < get_proximity_to(p_b) ? 0 : 1);
+ }
+
+ uint32_t find_cutting_planes(const BVH_ABB::ConvexHull &p_hull, uint32_t *p_plane_ids) const {
+ uint32_t count = 0;
+
+ for (int n = 0; n < p_hull.num_planes; n++) {
+ const Plane &p = p_hull.planes[n];
+ if (intersects_plane(p)) {
+ p_plane_ids[count++] = n;
+ }
+ }
+
+ return count;
+ }
+
+ bool intersects_plane(const Plane &p_p) const {
+ Vector3 size = calculate_size();
+ Vector3 half_extents = size * 0.5;
+ Vector3 ofs = min + half_extents;
+
+ // forward side of plane?
+ Vector3 point_offset(
+ (p_p.normal.x < 0) ? -half_extents.x : half_extents.x,
+ (p_p.normal.y < 0) ? -half_extents.y : half_extents.y,
+ (p_p.normal.z < 0) ? -half_extents.z : half_extents.z);
+ Vector3 point = point_offset + ofs;
+
+ if (!p_p.is_point_over(point)) {
+ return false;
+ }
+
+ point = -point_offset + ofs;
+ if (p_p.is_point_over(point)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool intersects_convex_optimized(const ConvexHull &p_hull, const uint32_t *p_plane_ids, uint32_t p_num_planes) const {
+ Vector3 size = calculate_size();
+ Vector3 half_extents = size * 0.5;
+ Vector3 ofs = min + half_extents;
+
+ for (unsigned int i = 0; i < p_num_planes; i++) {
+ const Plane &p = p_hull.planes[p_plane_ids[i]];
+ Vector3 point(
+ (p.normal.x > 0) ? -half_extents.x : half_extents.x,
+ (p.normal.y > 0) ? -half_extents.y : half_extents.y,
+ (p.normal.z > 0) ? -half_extents.z : half_extents.z);
+ point += ofs;
+ if (p.is_point_over(point)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool intersects_convex_partial(const ConvexHull &p_hull) const {
+ Bounds bb;
+ to(bb);
+ return bb.intersects_convex_shape(p_hull.planes, p_hull.num_planes, p_hull.points, p_hull.num_points);
+ }
+
+ IntersectResult intersects_convex(const ConvexHull &p_hull) const {
+ if (intersects_convex_partial(p_hull)) {
+ // fully within? very important for tree checks
+ if (is_within_convex(p_hull)) {
+ return IR_FULL;
+ }
+
+ return IR_PARTIAL;
+ }
+
+ return IR_MISS;
+ }
+
+ bool is_within_convex(const ConvexHull &p_hull) const {
+ // use half extents routine
+ Bounds bb;
+ to(bb);
+ return bb.inside_convex_shape(p_hull.planes, p_hull.num_planes);
+ }
+
+ bool is_point_within_hull(const ConvexHull &p_hull, const Vector3 &p_pt) const {
+ for (int n = 0; n < p_hull.num_planes; n++) {
+ if (p_hull.planes[n].distance_to(p_pt) > 0.0f) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool intersects_segment(const Segment &p_s) const {
+ Bounds bb;
+ to(bb);
+ return bb.intersects_segment(p_s.from, p_s.to);
+ }
+
+ bool intersects_point(const Point &p_pt) const {
+ if (_any_lessthan(-p_pt, neg_max)) {
+ return false;
+ }
+ if (_any_lessthan(p_pt, min)) {
+ return false;
+ }
+ return true;
+ }
+
+ bool intersects(const BVH_ABB &p_o) const {
+ if (_any_morethan(p_o.min, -neg_max)) {
+ return false;
+ }
+ if (_any_morethan(min, -p_o.neg_max)) {
+ return false;
+ }
+ return true;
+ }
+
+ bool is_other_within(const BVH_ABB &p_o) const {
+ if (_any_lessthan(p_o.neg_max, neg_max)) {
+ return false;
+ }
+ if (_any_lessthan(p_o.min, min)) {
+ return false;
+ }
+ return true;
+ }
+
+ void grow(const Point &p_change) {
+ neg_max -= p_change;
+ min -= p_change;
+ }
+
+ void expand(real_t p_change) {
+ Point change;
+ change.set_all(p_change);
+ grow(change);
+ }
+
+ // Actually surface area metric.
+ float get_area() const {
+ Point d = calculate_size();
+ return 2.0f * (d.x * d.y + d.y * d.z + d.z * d.x);
+ }
+
+ void set_to_max_opposite_extents() {
+ neg_max.set_all(FLT_MAX);
+ min = neg_max;
+ }
+
+ bool _any_morethan(const Point &p_a, const Point &p_b) const {
+ for (int axis = 0; axis < Point::AXIS_COUNT; ++axis) {
+ if (p_a[axis] > p_b[axis]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool _any_lessthan(const Point &p_a, const Point &p_b) const {
+ for (int axis = 0; axis < Point::AXIS_COUNT; ++axis) {
+ if (p_a[axis] < p_b[axis]) {
+ return true;
+ }
+ }
+ return false;
+ }
+};
+
+#endif // BVH_ABB_H
diff --git a/core/math/bvh_cull.inc b/core/math/bvh_cull.inc
new file mode 100644
index 0000000000..cba8ea6cb3
--- /dev/null
+++ b/core/math/bvh_cull.inc
@@ -0,0 +1,534 @@
+public:
+// cull parameters is a convenient way of passing a bunch
+// of arguments through the culling functions without
+// writing loads of code. Not all members are used for some cull checks
+struct CullParams {
+ int result_count_overall; // both trees
+ int result_count; // this tree only
+ int result_max;
+ T **result_array;
+ int *subindex_array;
+
+ // nobody truly understands how masks are intended to work.
+ uint32_t mask;
+ uint32_t pairable_type;
+
+ // optional components for different tests
+ Vector3 point;
+ BVHABB_CLASS abb;
+ typename BVHABB_CLASS::ConvexHull hull;
+ typename BVHABB_CLASS::Segment segment;
+
+ // when collision testing, non pairable moving items
+ // only need to be tested against the pairable tree.
+ // collisions with other non pairable items are irrelevant.
+ bool test_pairable_only;
+};
+
+private:
+void _cull_translate_hits(CullParams &p) {
+ int num_hits = _cull_hits.size();
+ int left = p.result_max - p.result_count_overall;
+
+ if (num_hits > left) {
+ num_hits = left;
+ }
+
+ int out_n = p.result_count_overall;
+
+ for (int n = 0; n < num_hits; n++) {
+ uint32_t ref_id = _cull_hits[n];
+
+ const ItemExtra &ex = _extra[ref_id];
+ p.result_array[out_n] = ex.userdata;
+
+ if (p.subindex_array) {
+ p.subindex_array[out_n] = ex.subindex;
+ }
+
+ out_n++;
+ }
+
+ p.result_count = num_hits;
+ p.result_count_overall += num_hits;
+}
+
+public:
+int cull_convex(CullParams &r_params, bool p_translate_hits = true) {
+ _cull_hits.clear();
+ r_params.result_count = 0;
+
+ for (int n = 0; n < NUM_TREES; n++) {
+ if (_root_node_id[n] == BVHCommon::INVALID) {
+ continue;
+ }
+
+ _cull_convex_iterative(_root_node_id[n], r_params);
+ }
+
+ if (p_translate_hits) {
+ _cull_translate_hits(r_params);
+ }
+
+ return r_params.result_count;
+}
+
+int cull_segment(CullParams &r_params, bool p_translate_hits = true) {
+ _cull_hits.clear();
+ r_params.result_count = 0;
+
+ for (int n = 0; n < NUM_TREES; n++) {
+ if (_root_node_id[n] == BVHCommon::INVALID) {
+ continue;
+ }
+
+ _cull_segment_iterative(_root_node_id[n], r_params);
+ }
+
+ if (p_translate_hits) {
+ _cull_translate_hits(r_params);
+ }
+
+ return r_params.result_count;
+}
+
+int cull_point(CullParams &r_params, bool p_translate_hits = true) {
+ _cull_hits.clear();
+ r_params.result_count = 0;
+
+ for (int n = 0; n < NUM_TREES; n++) {
+ if (_root_node_id[n] == BVHCommon::INVALID) {
+ continue;
+ }
+
+ _cull_point_iterative(_root_node_id[n], r_params);
+ }
+
+ if (p_translate_hits) {
+ _cull_translate_hits(r_params);
+ }
+
+ return r_params.result_count;
+}
+
+int cull_aabb(CullParams &r_params, bool p_translate_hits = true) {
+ _cull_hits.clear();
+ r_params.result_count = 0;
+
+ for (int n = 0; n < NUM_TREES; n++) {
+ if (_root_node_id[n] == BVHCommon::INVALID) {
+ continue;
+ }
+
+ if ((n == 0) && r_params.test_pairable_only) {
+ continue;
+ }
+
+ _cull_aabb_iterative(_root_node_id[n], r_params);
+ }
+
+ if (p_translate_hits) {
+ _cull_translate_hits(r_params);
+ }
+
+ return r_params.result_count;
+}
+
+bool _cull_hits_full(const CullParams &p) {
+ // instead of checking every hit, we can do a lazy check for this condition.
+ // it isn't a problem if we write too much _cull_hits because they only the
+ // result_max amount will be translated and outputted. But we might as
+ // well stop our cull checks after the maximum has been reached.
+ return (int)_cull_hits.size() >= p.result_max;
+}
+
+// write this logic once for use in all routines
+// double check this as a possible source of bugs in future.
+bool _cull_pairing_mask_test_hit(uint32_t p_maskA, uint32_t p_typeA, uint32_t p_maskB, uint32_t p_typeB) const {
+ // double check this as a possible source of bugs in future.
+ bool A_match_B = p_maskA & p_typeB;
+
+ if (!A_match_B) {
+ bool B_match_A = p_maskB & p_typeA;
+ if (!B_match_A) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void _cull_hit(uint32_t p_ref_id, CullParams &p) {
+ // take into account masks etc
+ // this would be more efficient to do before plane checks,
+ // but done here for ease to get started
+ if (USE_PAIRS) {
+ const ItemExtra &ex = _extra[p_ref_id];
+
+ if (!_cull_pairing_mask_test_hit(p.mask, p.pairable_type, ex.pairable_mask, ex.pairable_type)) {
+ return;
+ }
+ }
+
+ _cull_hits.push_back(p_ref_id);
+}
+
+bool _cull_segment_iterative(uint32_t p_node_id, CullParams &r_params) {
+ // our function parameters to keep on a stack
+ struct CullSegParams {
+ uint32_t node_id;
+ };
+
+ // most of the iterative functionality is contained in this helper class
+ BVH_IterativeInfo<CullSegParams> ii;
+
+ // alloca must allocate the stack from this function, it cannot be allocated in the
+ // helper class
+ ii.stack = (CullSegParams *)alloca(ii.get_alloca_stacksize());
+
+ // seed the stack
+ ii.get_first()->node_id = p_node_id;
+
+ CullSegParams csp;
+
+ // while there are still more nodes on the stack
+ while (ii.pop(csp)) {
+ TNode &tnode = _nodes[csp.node_id];
+
+ if (tnode.is_leaf()) {
+ // lazy check for hits full up condition
+ if (_cull_hits_full(r_params)) {
+ return false;
+ }
+
+ TLeaf &leaf = _node_get_leaf(tnode);
+
+ // test children individually
+ for (int n = 0; n < leaf.num_items; n++) {
+ const BVHABB_CLASS &aabb = leaf.get_aabb(n);
+
+ if (aabb.intersects_segment(r_params.segment)) {
+ uint32_t child_id = leaf.get_item_ref_id(n);
+
+ // register hit
+ _cull_hit(child_id, r_params);
+ }
+ }
+ } else {
+ // test children individually
+ for (int n = 0; n < tnode.num_children; n++) {
+ uint32_t child_id = tnode.children[n];
+ const BVHABB_CLASS &child_abb = _nodes[child_id].aabb;
+
+ if (child_abb.intersects_segment(r_params.segment)) {
+ // add to the stack
+ CullSegParams *child = ii.request();
+ child->node_id = child_id;
+ }
+ }
+ }
+
+ } // while more nodes to pop
+
+ // true indicates results are not full
+ return true;
+}
+
+bool _cull_point_iterative(uint32_t p_node_id, CullParams &r_params) {
+ // our function parameters to keep on a stack
+ struct CullPointParams {
+ uint32_t node_id;
+ };
+
+ // most of the iterative functionality is contained in this helper class
+ BVH_IterativeInfo<CullPointParams> ii;
+
+ // alloca must allocate the stack from this function, it cannot be allocated in the
+ // helper class
+ ii.stack = (CullPointParams *)alloca(ii.get_alloca_stacksize());
+
+ // seed the stack
+ ii.get_first()->node_id = p_node_id;
+
+ CullPointParams cpp;
+
+ // while there are still more nodes on the stack
+ while (ii.pop(cpp)) {
+ TNode &tnode = _nodes[cpp.node_id];
+ // no hit with this node?
+ if (!tnode.aabb.intersects_point(r_params.point)) {
+ continue;
+ }
+
+ if (tnode.is_leaf()) {
+ // lazy check for hits full up condition
+ if (_cull_hits_full(r_params)) {
+ return false;
+ }
+
+ TLeaf &leaf = _node_get_leaf(tnode);
+
+ // test children individually
+ for (int n = 0; n < leaf.num_items; n++) {
+ if (leaf.get_aabb(n).intersects_point(r_params.point)) {
+ uint32_t child_id = leaf.get_item_ref_id(n);
+
+ // register hit
+ _cull_hit(child_id, r_params);
+ }
+ }
+ } else {
+ // test children individually
+ for (int n = 0; n < tnode.num_children; n++) {
+ uint32_t child_id = tnode.children[n];
+
+ // add to the stack
+ CullPointParams *child = ii.request();
+ child->node_id = child_id;
+ }
+ }
+
+ } // while more nodes to pop
+
+ // true indicates results are not full
+ return true;
+}
+
+bool _cull_aabb_iterative(uint32_t p_node_id, CullParams &r_params, bool p_fully_within = false) {
+ // our function parameters to keep on a stack
+ struct CullAABBParams {
+ uint32_t node_id;
+ bool fully_within;
+ };
+
+ // most of the iterative functionality is contained in this helper class
+ BVH_IterativeInfo<CullAABBParams> ii;
+
+ // alloca must allocate the stack from this function, it cannot be allocated in the
+ // helper class
+ ii.stack = (CullAABBParams *)alloca(ii.get_alloca_stacksize());
+
+ // seed the stack
+ ii.get_first()->node_id = p_node_id;
+ ii.get_first()->fully_within = p_fully_within;
+
+ CullAABBParams cap;
+
+ // while there are still more nodes on the stack
+ while (ii.pop(cap)) {
+ TNode &tnode = _nodes[cap.node_id];
+
+ if (tnode.is_leaf()) {
+ // lazy check for hits full up condition
+ if (_cull_hits_full(r_params)) {
+ return false;
+ }
+
+ TLeaf &leaf = _node_get_leaf(tnode);
+
+ // if fully within we can just add all items
+ // as long as they pass mask checks
+ if (cap.fully_within) {
+ for (int n = 0; n < leaf.num_items; n++) {
+ uint32_t child_id = leaf.get_item_ref_id(n);
+
+ // register hit
+ _cull_hit(child_id, r_params);
+ }
+ } else {
+ for (int n = 0; n < leaf.num_items; n++) {
+ const BVHABB_CLASS &aabb = leaf.get_aabb(n);
+
+ if (aabb.intersects(r_params.abb)) {
+ uint32_t child_id = leaf.get_item_ref_id(n);
+
+ // register hit
+ _cull_hit(child_id, r_params);
+ }
+ }
+ } // not fully within
+ } else {
+ if (!cap.fully_within) {
+ // test children individually
+ for (int n = 0; n < tnode.num_children; n++) {
+ uint32_t child_id = tnode.children[n];
+ const BVHABB_CLASS &child_abb = _nodes[child_id].aabb;
+
+ if (child_abb.intersects(r_params.abb)) {
+ // is the node totally within the aabb?
+ bool fully_within = r_params.abb.is_other_within(child_abb);
+
+ // add to the stack
+ CullAABBParams *child = ii.request();
+
+ // should always return valid child
+ child->node_id = child_id;
+ child->fully_within = fully_within;
+ }
+ }
+ } else {
+ for (int n = 0; n < tnode.num_children; n++) {
+ uint32_t child_id = tnode.children[n];
+
+ // add to the stack
+ CullAABBParams *child = ii.request();
+
+ // should always return valid child
+ child->node_id = child_id;
+ child->fully_within = true;
+ }
+ }
+ }
+
+ } // while more nodes to pop
+
+ // true indicates results are not full
+ return true;
+}
+
+// returns full up with results
+bool _cull_convex_iterative(uint32_t p_node_id, CullParams &r_params, bool p_fully_within = false) {
+ // our function parameters to keep on a stack
+ struct CullConvexParams {
+ uint32_t node_id;
+ bool fully_within;
+ };
+
+ // most of the iterative functionality is contained in this helper class
+ BVH_IterativeInfo<CullConvexParams> ii;
+
+ // alloca must allocate the stack from this function, it cannot be allocated in the
+ // helper class
+ ii.stack = (CullConvexParams *)alloca(ii.get_alloca_stacksize());
+
+ // seed the stack
+ ii.get_first()->node_id = p_node_id;
+ ii.get_first()->fully_within = p_fully_within;
+
+ // preallocate these as a once off to be reused
+ uint32_t max_planes = r_params.hull.num_planes;
+ uint32_t *plane_ids = (uint32_t *)alloca(sizeof(uint32_t) * max_planes);
+
+ CullConvexParams ccp;
+
+ // while there are still more nodes on the stack
+ while (ii.pop(ccp)) {
+ const TNode &tnode = _nodes[ccp.node_id];
+
+ if (!ccp.fully_within) {
+ typename BVHABB_CLASS::IntersectResult res = tnode.aabb.intersects_convex(r_params.hull);
+
+ switch (res) {
+ default: {
+ continue; // miss, just move on to the next node in the stack
+ } break;
+ case BVHABB_CLASS::IR_PARTIAL: {
+ } break;
+ case BVHABB_CLASS::IR_FULL: {
+ ccp.fully_within = true;
+ } break;
+ }
+
+ } // if not fully within already
+
+ if (tnode.is_leaf()) {
+ // lazy check for hits full up condition
+ if (_cull_hits_full(r_params)) {
+ return false;
+ }
+
+ const TLeaf &leaf = _node_get_leaf(tnode);
+
+ // if fully within, simply add all items to the result
+ // (taking into account masks)
+ if (ccp.fully_within) {
+ for (int n = 0; n < leaf.num_items; n++) {
+ uint32_t child_id = leaf.get_item_ref_id(n);
+
+ // register hit
+ _cull_hit(child_id, r_params);
+ }
+
+ } else {
+ // we can either use a naive check of all the planes against the AABB,
+ // or an optimized check, which finds in advance which of the planes can possibly
+ // cut the AABB, and only tests those. This can be much faster.
+#define BVH_CONVEX_CULL_OPTIMIZED
+#ifdef BVH_CONVEX_CULL_OPTIMIZED
+ // first find which planes cut the aabb
+ uint32_t num_planes = tnode.aabb.find_cutting_planes(r_params.hull, plane_ids);
+ BVH_ASSERT(num_planes <= max_planes);
+
+//#define BVH_CONVEX_CULL_OPTIMIZED_RIGOR_CHECK
+#ifdef BVH_CONVEX_CULL_OPTIMIZED_RIGOR_CHECK
+ // rigorous check
+ uint32_t results[MAX_ITEMS];
+ uint32_t num_results = 0;
+#endif
+
+ // test children individually
+ for (int n = 0; n < leaf.num_items; n++) {
+ //const Item &item = leaf.get_item(n);
+ const BVHABB_CLASS &aabb = leaf.get_aabb(n);
+
+ if (aabb.intersects_convex_optimized(r_params.hull, plane_ids, num_planes)) {
+ uint32_t child_id = leaf.get_item_ref_id(n);
+
+#ifdef BVH_CONVEX_CULL_OPTIMIZED_RIGOR_CHECK
+ results[num_results++] = child_id;
+#endif
+
+ // register hit
+ _cull_hit(child_id, r_params);
+ }
+ }
+
+#ifdef BVH_CONVEX_CULL_OPTIMIZED_RIGOR_CHECK
+ uint32_t test_count = 0;
+
+ for (int n = 0; n < leaf.num_items; n++) {
+ const BVHABB_CLASS &aabb = leaf.get_aabb(n);
+
+ if (aabb.intersects_convex_partial(r_params.hull)) {
+ uint32_t child_id = leaf.get_item_ref_id(n);
+
+ CRASH_COND(child_id != results[test_count++]);
+ CRASH_COND(test_count > num_results);
+ }
+ }
+#endif
+
+#else
+ // not BVH_CONVEX_CULL_OPTIMIZED
+ // test children individually
+ for (int n = 0; n < leaf.num_items; n++) {
+ const BVHABB_CLASS &aabb = leaf.get_aabb(n);
+
+ if (aabb.intersects_convex_partial(r_params.hull)) {
+ uint32_t child_id = leaf.get_item_ref_id(n);
+
+ // full up with results? exit early, no point in further testing
+ if (!_cull_hit(child_id, r_params))
+ return false;
+ }
+ }
+#endif // BVH_CONVEX_CULL_OPTIMIZED
+ } // if not fully within
+ } else {
+ for (int n = 0; n < tnode.num_children; n++) {
+ uint32_t child_id = tnode.children[n];
+
+ // add to the stack
+ CullConvexParams *child = ii.request();
+
+ // should always return valid child
+ child->node_id = child_id;
+ child->fully_within = ccp.fully_within;
+ }
+ }
+
+ } // while more nodes to pop
+
+ // true indicates results are not full
+ return true;
+}
diff --git a/core/math/bvh_debug.inc b/core/math/bvh_debug.inc
new file mode 100644
index 0000000000..a97304334c
--- /dev/null
+++ b/core/math/bvh_debug.inc
@@ -0,0 +1,68 @@
+public:
+#ifdef BVH_VERBOSE
+void _debug_recursive_print_tree(int p_tree_id) const {
+ if (_root_node_id[p_tree_id] != BVHCommon::INVALID)
+ _debug_recursive_print_tree_node(_root_node_id[p_tree_id]);
+}
+
+String _debug_aabb_to_string(const BVHABB_CLASS &aabb) const {
+ String sz = "(";
+ sz += itos(aabb.min.x);
+ sz += " ~ ";
+ sz += itos(-aabb.neg_max.x);
+ sz += ") (";
+
+ sz += itos(aabb.min.y);
+ sz += " ~ ";
+ sz += itos(-aabb.neg_max.y);
+ sz += ") (";
+
+ sz += itos(aabb.min.z);
+ sz += " ~ ";
+ sz += itos(-aabb.neg_max.z);
+ sz += ") ";
+
+ Vector3 size = aabb.calculate_size();
+ float vol = size.x * size.y * size.z;
+ sz += "vol " + itos(vol);
+
+ return sz;
+}
+
+void _debug_recursive_print_tree_node(uint32_t p_node_id, int depth = 0) const {
+ const TNode &tnode = _nodes[p_node_id];
+
+ String sz = "";
+ for (int n = 0; n < depth; n++) {
+ sz += "\t";
+ }
+ sz += itos(p_node_id);
+
+ if (tnode.is_leaf()) {
+ sz += " L";
+ sz += itos(tnode.height) + " ";
+ const TLeaf &leaf = _node_get_leaf(tnode);
+
+ sz += "[";
+ for (int n = 0; n < leaf.num_items; n++) {
+ if (n)
+ sz += ", ";
+ sz += "r";
+ sz += itos(leaf.get_item_ref_id(n));
+ }
+ sz += "] ";
+ } else {
+ sz += " N";
+ sz += itos(tnode.height) + " ";
+ }
+
+ sz += _debug_aabb_to_string(tnode.aabb);
+ print_line(sz);
+
+ if (!tnode.is_leaf()) {
+ for (int n = 0; n < tnode.num_children; n++) {
+ _debug_recursive_print_tree_node(tnode.children[n], depth + 1);
+ }
+ }
+}
+#endif
diff --git a/core/math/bvh_integrity.inc b/core/math/bvh_integrity.inc
new file mode 100644
index 0000000000..02e9d30097
--- /dev/null
+++ b/core/math/bvh_integrity.inc
@@ -0,0 +1,42 @@
+void _integrity_check_all() {
+#ifdef BVH_INTEGRITY_CHECKS
+ for (int n = 0; n < NUM_TREES; n++) {
+ uint32_t root = _root_node_id[n];
+ if (root != BVHCommon::INVALID) {
+ _integrity_check_down(root);
+ }
+ }
+#endif
+}
+
+void _integrity_check_up(uint32_t p_node_id) {
+ TNode &node = _nodes[p_node_id];
+
+ BVHABB_CLASS abb = node.aabb;
+ node_update_aabb(node);
+
+ BVHABB_CLASS abb2 = node.aabb;
+ abb2.expand(-_node_expansion);
+
+ CRASH_COND(!abb.is_other_within(abb2));
+}
+
+void _integrity_check_down(uint32_t p_node_id) {
+ const TNode &node = _nodes[p_node_id];
+
+ if (node.is_leaf()) {
+ _integrity_check_up(p_node_id);
+ } else {
+ CRASH_COND(node.num_children != 2);
+
+ for (int n = 0; n < node.num_children; n++) {
+ uint32_t child_id = node.children[n];
+
+ // check the children parent pointers are correct
+ TNode &child = _nodes[child_id];
+ CRASH_COND(child.parent_id != p_node_id);
+
+ _integrity_check_down(child_id);
+ }
+ }
+}
diff --git a/core/math/bvh_logic.inc b/core/math/bvh_logic.inc
new file mode 100644
index 0000000000..d84c3f7830
--- /dev/null
+++ b/core/math/bvh_logic.inc
@@ -0,0 +1,230 @@
+
+// for slow incremental optimization, we will periodically remove each
+// item from the tree and reinsert, to give it a chance to find a better position
+void _logic_item_remove_and_reinsert(uint32_t p_ref_id) {
+ // get the reference
+ ItemRef &ref = _refs[p_ref_id];
+
+ // no need to optimize inactive items
+ if (!ref.is_active()) {
+ return;
+ }
+
+ // special case of debug draw
+ if (ref.item_id == BVHCommon::INVALID) {
+ return;
+ }
+
+ BVH_ASSERT(ref.tnode_id != BVHCommon::INVALID);
+
+ // some overlay elaborate way to find out which tree the node is in!
+ BVHHandle temp_handle;
+ temp_handle.set_id(p_ref_id);
+ _current_tree = _handle_get_tree_id(temp_handle);
+
+ // remove and reinsert
+ BVHABB_CLASS abb;
+ node_remove_item(p_ref_id, &abb);
+
+ // we must choose where to add to tree
+ ref.tnode_id = _logic_choose_item_add_node(_root_node_id[_current_tree], abb);
+ _node_add_item(ref.tnode_id, p_ref_id, abb);
+
+ refit_upward_and_balance(ref.tnode_id);
+}
+
+// from randy gaul balance function
+BVHABB_CLASS _logic_abb_merge(const BVHABB_CLASS &a, const BVHABB_CLASS &b) {
+ BVHABB_CLASS c = a;
+ c.merge(b);
+ return c;
+}
+
+//--------------------------------------------------------------------------------------------------
+/**
+@file q3DynamicAABBTree.h
+@author Randy Gaul
+@date 10/10/2014
+ Copyright (c) 2014 Randy Gaul http://www.randygaul.net
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not
+ be misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+//--------------------------------------------------------------------------------------------------
+
+// This function is based on the 'Balance' function from Randy Gaul's qu3e
+// https://github.com/RandyGaul/qu3e
+// It is MODIFIED from qu3e version.
+// This is the only function used (and _logic_abb_merge helper function).
+int32_t _logic_balance(int32_t iA) {
+ // return iA; // uncomment this to bypass balance
+
+ TNode *A = &_nodes[iA];
+
+ if (A->is_leaf() || A->height == 1) {
+ return iA;
+ }
+
+ /* A
+ / \
+ B C
+ / \ / \
+ D E F G
+ */
+
+ CRASH_COND(A->num_children != 2);
+ int32_t iB = A->children[0];
+ int32_t iC = A->children[1];
+ TNode *B = &_nodes[iB];
+ TNode *C = &_nodes[iC];
+
+ int32_t balance = C->height - B->height;
+
+ // C is higher, promote C
+ if (balance > 1) {
+ int32_t iF = C->children[0];
+ int32_t iG = C->children[1];
+ TNode *F = &_nodes[iF];
+ TNode *G = &_nodes[iG];
+
+ // grandParent point to C
+ if (A->parent_id != BVHCommon::INVALID) {
+ if (_nodes[A->parent_id].children[0] == iA) {
+ _nodes[A->parent_id].children[0] = iC;
+
+ } else {
+ _nodes[A->parent_id].children[1] = iC;
+ }
+ } else {
+ // check this .. seems dodgy
+ change_root_node(iC);
+ }
+
+ // Swap A and C
+ C->children[0] = iA;
+ C->parent_id = A->parent_id;
+ A->parent_id = iC;
+
+ // Finish rotation
+ if (F->height > G->height) {
+ C->children[1] = iF;
+ A->children[1] = iG;
+ G->parent_id = iA;
+ A->aabb = _logic_abb_merge(B->aabb, G->aabb);
+ C->aabb = _logic_abb_merge(A->aabb, F->aabb);
+
+ A->height = 1 + MAX(B->height, G->height);
+ C->height = 1 + MAX(A->height, F->height);
+ }
+
+ else {
+ C->children[1] = iG;
+ A->children[1] = iF;
+ F->parent_id = iA;
+ A->aabb = _logic_abb_merge(B->aabb, F->aabb);
+ C->aabb = _logic_abb_merge(A->aabb, G->aabb);
+
+ A->height = 1 + MAX(B->height, F->height);
+ C->height = 1 + MAX(A->height, G->height);
+ }
+
+ return iC;
+ }
+
+ // B is higher, promote B
+ else if (balance < -1) {
+ int32_t iD = B->children[0];
+ int32_t iE = B->children[1];
+ TNode *D = &_nodes[iD];
+ TNode *E = &_nodes[iE];
+
+ // grandParent point to B
+ if (A->parent_id != BVHCommon::INVALID) {
+ if (_nodes[A->parent_id].children[0] == iA) {
+ _nodes[A->parent_id].children[0] = iB;
+ } else {
+ _nodes[A->parent_id].children[1] = iB;
+ }
+ }
+
+ else {
+ // check this .. seems dodgy
+ change_root_node(iB);
+ }
+
+ // Swap A and B
+ B->children[1] = iA;
+ B->parent_id = A->parent_id;
+ A->parent_id = iB;
+
+ // Finish rotation
+ if (D->height > E->height) {
+ B->children[0] = iD;
+ A->children[0] = iE;
+ E->parent_id = iA;
+ A->aabb = _logic_abb_merge(C->aabb, E->aabb);
+ B->aabb = _logic_abb_merge(A->aabb, D->aabb);
+
+ A->height = 1 + MAX(C->height, E->height);
+ B->height = 1 + MAX(A->height, D->height);
+ }
+
+ else {
+ B->children[0] = iE;
+ A->children[0] = iD;
+ D->parent_id = iA;
+ A->aabb = _logic_abb_merge(C->aabb, D->aabb);
+ B->aabb = _logic_abb_merge(A->aabb, E->aabb);
+
+ A->height = 1 + MAX(C->height, D->height);
+ B->height = 1 + MAX(A->height, E->height);
+ }
+
+ return iB;
+ }
+
+ return iA;
+}
+
+// either choose an existing node to add item to, or create a new node and return this
+uint32_t _logic_choose_item_add_node(uint32_t p_node_id, const BVHABB_CLASS &p_aabb) {
+ while (true) {
+ BVH_ASSERT(p_node_id != BVHCommon::INVALID);
+ TNode &tnode = _nodes[p_node_id];
+
+ if (tnode.is_leaf()) {
+ // if a leaf, and non full, use this to add to
+ if (!node_is_leaf_full(tnode)) {
+ return p_node_id;
+ }
+
+ // else split the leaf, and use one of the children to add to
+ return split_leaf(p_node_id, p_aabb);
+ }
+
+ // this should not happen???
+ // is still happening, need to debug and find circumstances. Is not that serious
+ // but would be nice to prevent. I think it only happens with the root node.
+ if (tnode.num_children == 1) {
+ WARN_PRINT_ONCE("BVH::recursive_choose_item_add_node, node with 1 child, recovering");
+ p_node_id = tnode.children[0];
+ } else {
+ BVH_ASSERT(tnode.num_children == 2);
+ TNode &childA = _nodes[tnode.children[0]];
+ TNode &childB = _nodes[tnode.children[1]];
+ int which = p_aabb.select_by_proximity(childA.aabb, childB.aabb);
+
+ p_node_id = tnode.children[which];
+ }
+ }
+}
diff --git a/core/math/bvh_misc.inc b/core/math/bvh_misc.inc
new file mode 100644
index 0000000000..71aa0e4fe0
--- /dev/null
+++ b/core/math/bvh_misc.inc
@@ -0,0 +1,55 @@
+
+int _handle_get_tree_id(BVHHandle p_handle) const {
+ if (USE_PAIRS) {
+ int tree = 0;
+ if (_extra[p_handle.id()].pairable) {
+ tree = 1;
+ }
+ return tree;
+ }
+ return 0;
+}
+
+public:
+void _handle_sort(BVHHandle &p_ha, BVHHandle &p_hb) const {
+ if (p_ha.id() > p_hb.id()) {
+ BVHHandle temp = p_hb;
+ p_hb = p_ha;
+ p_ha = temp;
+ }
+}
+
+private:
+void create_root_node(int p_tree) {
+ // if there is no root node, create one
+ if (_root_node_id[p_tree] == BVHCommon::INVALID) {
+ uint32_t root_node_id;
+ TNode *node = _nodes.request(root_node_id);
+ node->clear();
+ _root_node_id[p_tree] = root_node_id;
+
+ // make the root node a leaf
+ uint32_t leaf_id;
+ TLeaf *leaf = _leaves.request(leaf_id);
+ leaf->clear();
+ node->neg_leaf_id = -(int)leaf_id;
+ }
+}
+
+bool node_is_leaf_full(TNode &tnode) const {
+ const TLeaf &leaf = _node_get_leaf(tnode);
+ return leaf.is_full();
+}
+
+public:
+TLeaf &_node_get_leaf(TNode &tnode) {
+ BVH_ASSERT(tnode.is_leaf());
+ return _leaves[tnode.get_leaf_id()];
+}
+
+const TLeaf &_node_get_leaf(const TNode &tnode) const {
+ BVH_ASSERT(tnode.is_leaf());
+ return _leaves[tnode.get_leaf_id()];
+}
+
+private:
diff --git a/core/math/bvh_pair.inc b/core/math/bvh_pair.inc
new file mode 100644
index 0000000000..839db59a3a
--- /dev/null
+++ b/core/math/bvh_pair.inc
@@ -0,0 +1,62 @@
+public:
+// note .. maybe this can be attached to another node structure?
+// depends which works best for cache.
+struct ItemPairs {
+ struct Link {
+ void set(BVHHandle h, void *ud) {
+ handle = h;
+ userdata = ud;
+ }
+ BVHHandle handle;
+ void *userdata;
+ };
+
+ void clear() {
+ num_pairs = 0;
+ extended_pairs.reset();
+ expanded_aabb = Bounds();
+ }
+
+ Bounds expanded_aabb;
+
+ // maybe we can just use the number in the vector TODO
+ int32_t num_pairs;
+ LocalVector<Link> extended_pairs;
+
+ void add_pair_to(BVHHandle h, void *p_userdata) {
+ Link temp;
+ temp.set(h, p_userdata);
+
+ extended_pairs.push_back(temp);
+ num_pairs++;
+ }
+
+ uint32_t find_pair_to(BVHHandle h) const {
+ for (int n = 0; n < num_pairs; n++) {
+ if (extended_pairs[n].handle == h) {
+ return n;
+ }
+ }
+ return -1;
+ }
+
+ bool contains_pair_to(BVHHandle h) const {
+ return find_pair_to(h) != BVHCommon::INVALID;
+ }
+
+ // return success
+ void *remove_pair_to(BVHHandle h) {
+ void *userdata = nullptr;
+
+ for (int n = 0; n < num_pairs; n++) {
+ if (extended_pairs[n].handle == h) {
+ userdata = extended_pairs[n].userdata;
+ extended_pairs.remove_unordered(n);
+ num_pairs--;
+ break;
+ }
+ }
+
+ return userdata;
+ }
+};
diff --git a/core/math/bvh_public.inc b/core/math/bvh_public.inc
new file mode 100644
index 0000000000..f1b6d6b1bf
--- /dev/null
+++ b/core/math/bvh_public.inc
@@ -0,0 +1,421 @@
+public:
+BVHHandle item_add(T *p_userdata, bool p_active, const Bounds &p_aabb, int32_t p_subindex, bool p_pairable, uint32_t p_pairable_type, uint32_t p_pairable_mask, bool p_invisible = false) {
+#ifdef BVH_VERBOSE_TREE
+ VERBOSE_PRINT("\nitem_add BEFORE");
+ _debug_recursive_print_tree(0);
+ VERBOSE_PRINT("\n");
+#endif
+
+ BVHABB_CLASS abb;
+ abb.from(p_aabb);
+
+ // handle to be filled with the new item ref
+ BVHHandle handle;
+
+ // ref id easier to pass around than handle
+ uint32_t ref_id;
+
+ // this should never fail
+ ItemRef *ref = _refs.request(ref_id);
+
+ // the extra data should be parallel list to the references
+ uint32_t extra_id;
+ ItemExtra *extra = _extra.request(extra_id);
+ BVH_ASSERT(extra_id == ref_id);
+
+ // pairs info
+ if (USE_PAIRS) {
+ uint32_t pairs_id;
+ ItemPairs *pairs = _pairs.request(pairs_id);
+ pairs->clear();
+ BVH_ASSERT(pairs_id == ref_id);
+ }
+
+ extra->subindex = p_subindex;
+ extra->userdata = p_userdata;
+ extra->last_updated_tick = 0;
+
+ // add an active reference to the list for slow incremental optimize
+ // this list must be kept in sync with the references as they are added or removed.
+ extra->active_ref_id = _active_refs.size();
+ _active_refs.push_back(ref_id);
+
+ if (USE_PAIRS) {
+ extra->pairable_mask = p_pairable_mask;
+ extra->pairable_type = p_pairable_type;
+ extra->pairable = p_pairable;
+ } else {
+ // just for safety, in case this gets queried etc
+ extra->pairable = 0;
+ p_pairable = false;
+ }
+
+ // assign to handle to return
+ handle.set_id(ref_id);
+
+ _current_tree = 0;
+ if (p_pairable) {
+ _current_tree = 1;
+ }
+
+ create_root_node(_current_tree);
+
+ // we must choose where to add to tree
+ if (p_active) {
+ ref->tnode_id = _logic_choose_item_add_node(_root_node_id[_current_tree], abb);
+
+ bool refit = _node_add_item(ref->tnode_id, ref_id, abb);
+
+ if (refit) {
+ // only need to refit from the parent
+ const TNode &add_node = _nodes[ref->tnode_id];
+ if (add_node.parent_id != BVHCommon::INVALID) {
+ refit_upward_and_balance(add_node.parent_id);
+ }
+ }
+ } else {
+ ref->set_inactive();
+ }
+
+#ifdef BVH_VERBOSE
+ // memory use
+ int mem = _refs.estimate_memory_use();
+ mem += _nodes.estimate_memory_use();
+
+ String sz = _debug_aabb_to_string(abb);
+ VERBOSE_PRINT("\titem_add [" + itos(ref_id) + "] " + itos(_refs.size()) + " refs,\t" + itos(_nodes.size()) + " nodes " + sz);
+ VERBOSE_PRINT("mem use : " + itos(mem) + ", num nodes : " + itos(_nodes.size()));
+
+#endif
+
+ return handle;
+}
+
+void _debug_print_refs() {
+#ifdef BVH_VERBOSE_TREE
+ print_line("refs.....");
+ for (int n = 0; n < _refs.size(); n++) {
+ const ItemRef &ref = _refs[n];
+ print_line("tnode_id " + itos(ref.tnode_id) + ", item_id " + itos(ref.item_id));
+ }
+
+#endif
+}
+
+// returns false if noop
+bool item_move(BVHHandle p_handle, const Bounds &p_aabb) {
+ uint32_t ref_id = p_handle.id();
+
+ // get the reference
+ ItemRef &ref = _refs[ref_id];
+ if (!ref.is_active()) {
+ return false;
+ }
+
+ BVHABB_CLASS abb;
+ abb.from(p_aabb);
+
+ BVH_ASSERT(ref.tnode_id != BVHCommon::INVALID);
+ TNode &tnode = _nodes[ref.tnode_id];
+
+ // does it fit within the current aabb?
+ if (tnode.aabb.is_other_within(abb)) {
+ // do nothing .. fast path .. not moved enough to need refit
+
+ // however we WILL update the exact aabb in the leaf, as this will be needed
+ // for accurate collision detection
+ TLeaf &leaf = _node_get_leaf(tnode);
+
+ BVHABB_CLASS &leaf_abb = leaf.get_aabb(ref.item_id);
+
+ // no change?
+ if (leaf_abb == abb) {
+ return false;
+ }
+
+ leaf_abb = abb;
+ _integrity_check_all();
+
+ return true;
+ }
+
+ _current_tree = _handle_get_tree_id(p_handle);
+
+ // remove and reinsert
+ node_remove_item(ref_id);
+
+ // we must choose where to add to tree
+ ref.tnode_id = _logic_choose_item_add_node(_root_node_id[_current_tree], abb);
+
+ // add to the tree
+ bool needs_refit = _node_add_item(ref.tnode_id, ref_id, abb);
+
+ // only need to refit from the PARENT
+ if (needs_refit) {
+ // only need to refit from the parent
+ const TNode &add_node = _nodes[ref.tnode_id];
+ if (add_node.parent_id != BVHCommon::INVALID) {
+ // not sure we need to rebalance all the time, this can be done less often
+ refit_upward(add_node.parent_id);
+ }
+ //refit_upward_and_balance(add_node.parent_id);
+ }
+
+ return true;
+}
+
+void item_remove(BVHHandle p_handle) {
+ uint32_t ref_id = p_handle.id();
+
+ _current_tree = _handle_get_tree_id(p_handle);
+
+ VERBOSE_PRINT("item_remove [" + itos(ref_id) + "] ");
+
+ ////////////////////////////////////////
+ // remove the active reference from the list for slow incremental optimize
+ // this list must be kept in sync with the references as they are added or removed.
+ uint32_t active_ref_id = _extra[ref_id].active_ref_id;
+ uint32_t ref_id_moved_back = _active_refs[_active_refs.size() - 1];
+
+ // swap back and decrement for fast unordered remove
+ _active_refs[active_ref_id] = ref_id_moved_back;
+ _active_refs.resize(_active_refs.size() - 1);
+
+ // keep the moved active reference up to date
+ _extra[ref_id_moved_back].active_ref_id = active_ref_id;
+ ////////////////////////////////////////
+
+ // remove the item from the node (only if active)
+ if (_refs[ref_id].is_active()) {
+ node_remove_item(ref_id);
+ }
+
+ // remove the item reference
+ _refs.free(ref_id);
+ _extra.free(ref_id);
+ if (USE_PAIRS) {
+ _pairs.free(ref_id);
+ }
+
+ // don't think refit_all is necessary?
+ //refit_all(_current_tree);
+
+#ifdef BVH_VERBOSE_TREE
+ _debug_recursive_print_tree(_current_tree);
+#endif
+}
+
+// returns success
+bool item_activate(BVHHandle p_handle, const Bounds &p_aabb) {
+ uint32_t ref_id = p_handle.id();
+ ItemRef &ref = _refs[ref_id];
+ if (ref.is_active()) {
+ // noop
+ return false;
+ }
+
+ // add to tree
+ BVHABB_CLASS abb;
+ abb.from(p_aabb);
+
+ _current_tree = _handle_get_tree_id(p_handle);
+
+ // we must choose where to add to tree
+ ref.tnode_id = _logic_choose_item_add_node(_root_node_id[_current_tree], abb);
+ _node_add_item(ref.tnode_id, ref_id, abb);
+
+ refit_upward_and_balance(ref.tnode_id);
+
+ return true;
+}
+
+// returns success
+bool item_deactivate(BVHHandle p_handle) {
+ uint32_t ref_id = p_handle.id();
+ ItemRef &ref = _refs[ref_id];
+ if (!ref.is_active()) {
+ // noop
+ return false;
+ }
+
+ // remove from tree
+ BVHABB_CLASS abb;
+ node_remove_item(ref_id, &abb);
+
+ // mark as inactive
+ ref.set_inactive();
+ return true;
+}
+
+bool item_get_active(BVHHandle p_handle) const {
+ uint32_t ref_id = p_handle.id();
+ const ItemRef &ref = _refs[ref_id];
+ return ref.is_active();
+}
+
+// during collision testing, we want to set the mask and whether pairable for the item testing from
+void item_fill_cullparams(BVHHandle p_handle, CullParams &r_params) const {
+ uint32_t ref_id = p_handle.id();
+ const ItemExtra &extra = _extra[ref_id];
+
+ // testing from a non pairable item, we only want to test pairable items
+ r_params.test_pairable_only = extra.pairable == 0;
+
+ // we take into account the mask of the item testing from
+ r_params.mask = extra.pairable_mask;
+ r_params.pairable_type = extra.pairable_type;
+}
+
+bool item_is_pairable(const BVHHandle &p_handle) {
+ uint32_t ref_id = p_handle.id();
+ const ItemExtra &extra = _extra[ref_id];
+ return extra.pairable != 0;
+}
+
+void item_get_ABB(const BVHHandle &p_handle, BVHABB_CLASS &r_abb) {
+ // change tree?
+ uint32_t ref_id = p_handle.id();
+ const ItemRef &ref = _refs[ref_id];
+
+ TNode &tnode = _nodes[ref.tnode_id];
+ TLeaf &leaf = _node_get_leaf(tnode);
+
+ r_abb = leaf.get_aabb(ref.item_id);
+}
+
+bool item_set_pairable(const BVHHandle &p_handle, bool p_pairable, uint32_t p_pairable_type, uint32_t p_pairable_mask) {
+ // change tree?
+ uint32_t ref_id = p_handle.id();
+
+ ItemExtra &ex = _extra[ref_id];
+ ItemRef &ref = _refs[ref_id];
+
+ bool active = ref.is_active();
+ bool pairable_changed = (ex.pairable != 0) != p_pairable;
+ bool state_changed = pairable_changed || (ex.pairable_type != p_pairable_type) || (ex.pairable_mask != p_pairable_mask);
+
+ ex.pairable_type = p_pairable_type;
+ ex.pairable_mask = p_pairable_mask;
+
+ if (active && pairable_changed) {
+ // record abb
+ TNode &tnode = _nodes[ref.tnode_id];
+ TLeaf &leaf = _node_get_leaf(tnode);
+ BVHABB_CLASS abb = leaf.get_aabb(ref.item_id);
+
+ // make sure current tree is correct prior to changing
+ _current_tree = _handle_get_tree_id(p_handle);
+
+ // remove from old tree
+ node_remove_item(ref_id);
+
+ // we must set the pairable AFTER getting the current tree
+ // because the pairable status determines which tree
+ ex.pairable = p_pairable;
+
+ // add to new tree
+ _current_tree = _handle_get_tree_id(p_handle);
+ create_root_node(_current_tree);
+
+ // we must choose where to add to tree
+ ref.tnode_id = _logic_choose_item_add_node(_root_node_id[_current_tree], abb);
+ bool needs_refit = _node_add_item(ref.tnode_id, ref_id, abb);
+
+ // only need to refit from the PARENT
+ if (needs_refit) {
+ // only need to refit from the parent
+ const TNode &add_node = _nodes[ref.tnode_id];
+ if (add_node.parent_id != BVHCommon::INVALID) {
+ refit_upward_and_balance(add_node.parent_id);
+ }
+ }
+ } else {
+ // always keep this up to date
+ ex.pairable = p_pairable;
+ }
+
+ return state_changed;
+}
+
+void incremental_optimize() {
+ // first update all aabbs as one off step..
+ // this is cheaper than doing it on each move as each leaf may get touched multiple times
+ // in a frame.
+ for (int n = 0; n < NUM_TREES; n++) {
+ if (_root_node_id[n] != BVHCommon::INVALID) {
+ refit_branch(_root_node_id[n]);
+ }
+ }
+
+ // now do small section reinserting to get things moving
+ // gradually, and keep items in the right leaf
+ if (_current_active_ref >= _active_refs.size()) {
+ _current_active_ref = 0;
+ }
+
+ // special case
+ if (!_active_refs.size()) {
+ return;
+ }
+
+ uint32_t ref_id = _active_refs[_current_active_ref++];
+
+ _logic_item_remove_and_reinsert(ref_id);
+
+#ifdef BVH_VERBOSE
+ /*
+ // memory use
+ int mem_refs = _refs.estimate_memory_use();
+ int mem_nodes = _nodes.estimate_memory_use();
+ int mem_leaves = _leaves.estimate_memory_use();
+
+ String sz;
+ sz += "mem_refs : " + itos(mem_refs) + " ";
+ sz += "mem_nodes : " + itos(mem_nodes) + " ";
+ sz += "mem_leaves : " + itos(mem_leaves) + " ";
+ sz += ", num nodes : " + itos(_nodes.size());
+ print_line(sz);
+ */
+#endif
+}
+
+void update() {
+ incremental_optimize();
+
+ // keep the expansion values up to date with the world bound
+//#define BVH_ALLOW_AUTO_EXPANSION
+#ifdef BVH_ALLOW_AUTO_EXPANSION
+ if (_auto_node_expansion || _auto_pairing_expansion) {
+ BVHABB_CLASS world_bound;
+ world_bound.set_to_max_opposite_extents();
+
+ bool bound_valid = false;
+
+ for (int n = 0; n < NUM_TREES; n++) {
+ uint32_t node_id = _root_node_id[n];
+ if (node_id != BVHCommon::INVALID) {
+ world_bound.merge(_nodes[node_id].aabb);
+ bound_valid = true;
+ }
+ }
+
+ // if there are no nodes, do nothing, but if there are...
+ if (bound_valid) {
+ Bounds bb;
+ world_bound.to(bb);
+ real_t size = bb.get_longest_axis_size();
+
+ // automatic AI decision for best parameters.
+ // These can be overridden in project settings.
+
+ // these magic numbers are determined by experiment
+ if (_auto_node_expansion) {
+ _node_expansion = size * 0.025;
+ }
+ if (_auto_pairing_expansion) {
+ _pairing_expansion = size * 0.009;
+ }
+ }
+ }
+#endif
+}
diff --git a/core/math/bvh_refit.inc b/core/math/bvh_refit.inc
new file mode 100644
index 0000000000..514c853ac5
--- /dev/null
+++ b/core/math/bvh_refit.inc
@@ -0,0 +1,141 @@
+void _debug_node_verify_bound(uint32_t p_node_id) {
+ TNode &node = _nodes[p_node_id];
+ BVHABB_CLASS abb_before = node.aabb;
+
+ node_update_aabb(node);
+
+ BVHABB_CLASS abb_after = node.aabb;
+ CRASH_COND(abb_before != abb_after);
+}
+
+void node_update_aabb(TNode &tnode) {
+ tnode.aabb.set_to_max_opposite_extents();
+ tnode.height = 0;
+
+ if (!tnode.is_leaf()) {
+ for (int n = 0; n < tnode.num_children; n++) {
+ uint32_t child_node_id = tnode.children[n];
+
+ // merge with child aabb
+ const TNode &tchild = _nodes[child_node_id];
+ tnode.aabb.merge(tchild.aabb);
+
+ // do heights at the same time
+ if (tchild.height > tnode.height) {
+ tnode.height = tchild.height;
+ }
+ }
+
+ // the height of a non leaf is always 1 bigger than the biggest child
+ tnode.height++;
+
+#ifdef BVH_CHECKS
+ if (!tnode.num_children) {
+ // the 'blank' aabb will screw up parent aabbs
+ WARN_PRINT("BVH_Tree::TNode no children, AABB is undefined");
+ }
+#endif
+ } else {
+ // leaf
+ const TLeaf &leaf = _node_get_leaf(tnode);
+
+ for (int n = 0; n < leaf.num_items; n++) {
+ tnode.aabb.merge(leaf.get_aabb(n));
+ }
+
+ // now the leaf items are unexpanded, we expand only in the node AABB
+ tnode.aabb.expand(_node_expansion);
+#ifdef BVH_CHECKS
+ if (!leaf.num_items) {
+ // the 'blank' aabb will screw up parent aabbs
+ WARN_PRINT("BVH_Tree::TLeaf no items, AABB is undefined");
+ }
+#endif
+ }
+}
+
+void refit_all(int p_tree_id) {
+ refit_downward(_root_node_id[p_tree_id]);
+}
+
+void refit_upward(uint32_t p_node_id) {
+ while (p_node_id != BVHCommon::INVALID) {
+ TNode &tnode = _nodes[p_node_id];
+ node_update_aabb(tnode);
+ p_node_id = tnode.parent_id;
+ }
+}
+
+void refit_upward_and_balance(uint32_t p_node_id) {
+ while (p_node_id != BVHCommon::INVALID) {
+ uint32_t before = p_node_id;
+ p_node_id = _logic_balance(p_node_id);
+
+ if (before != p_node_id) {
+ VERBOSE_PRINT("REBALANCED!");
+ }
+
+ TNode &tnode = _nodes[p_node_id];
+
+ // update overall aabb from the children
+ node_update_aabb(tnode);
+
+ p_node_id = tnode.parent_id;
+ }
+}
+
+void refit_downward(uint32_t p_node_id) {
+ TNode &tnode = _nodes[p_node_id];
+
+ // do children first
+ if (!tnode.is_leaf()) {
+ for (int n = 0; n < tnode.num_children; n++) {
+ refit_downward(tnode.children[n]);
+ }
+ }
+
+ node_update_aabb(tnode);
+}
+
+// go down to the leaves, then refit upward
+void refit_branch(uint32_t p_node_id) {
+ // our function parameters to keep on a stack
+ struct RefitParams {
+ uint32_t node_id;
+ };
+
+ // most of the iterative functionality is contained in this helper class
+ BVH_IterativeInfo<RefitParams> ii;
+
+ // alloca must allocate the stack from this function, it cannot be allocated in the
+ // helper class
+ ii.stack = (RefitParams *)alloca(ii.get_alloca_stacksize());
+
+ // seed the stack
+ ii.get_first()->node_id = p_node_id;
+
+ RefitParams rp;
+
+ // while there are still more nodes on the stack
+ while (ii.pop(rp)) {
+ TNode &tnode = _nodes[rp.node_id];
+
+ // do children first
+ if (!tnode.is_leaf()) {
+ for (int n = 0; n < tnode.num_children; n++) {
+ uint32_t child_id = tnode.children[n];
+
+ // add to the stack
+ RefitParams *child = ii.request();
+ child->node_id = child_id;
+ }
+ } else {
+ // leaf .. only refit upward if dirty
+ TLeaf &leaf = _node_get_leaf(tnode);
+ if (leaf.is_dirty()) {
+ leaf.set_dirty(false);
+ refit_upward(p_node_id);
+ }
+ }
+ } // while more nodes to pop
+}
diff --git a/core/math/bvh_split.inc b/core/math/bvh_split.inc
new file mode 100644
index 0000000000..3fcc4c7b10
--- /dev/null
+++ b/core/math/bvh_split.inc
@@ -0,0 +1,294 @@
+void _split_inform_references(uint32_t p_node_id) {
+ TNode &node = _nodes[p_node_id];
+ TLeaf &leaf = _node_get_leaf(node);
+
+ for (int n = 0; n < leaf.num_items; n++) {
+ uint32_t ref_id = leaf.get_item_ref_id(n);
+
+ ItemRef &ref = _refs[ref_id];
+ ref.tnode_id = p_node_id;
+ ref.item_id = n;
+ }
+}
+
+void _split_leaf_sort_groups_simple(int &num_a, int &num_b, uint16_t *group_a, uint16_t *group_b, const BVHABB_CLASS *temp_bounds, const BVHABB_CLASS full_bound) {
+ // special case for low leaf sizes .. should static compile out
+ if (MAX_ITEMS < 4) {
+ uint32_t ind = group_a[0];
+
+ // add to b
+ group_b[num_b++] = ind;
+
+ // remove from a
+ group_a[0] = group_a[num_a - 1];
+ num_a--;
+ return;
+ }
+
+ Point centre = full_bound.calculate_centre();
+ Point size = full_bound.calculate_size();
+
+ int order[3];
+
+ order[0] = size.min_axis();
+ order[2] = size.max_axis();
+ order[1] = 3 - (order[0] + order[2]);
+
+ // simplest case, split on the longest axis
+ int split_axis = order[0];
+ for (int a = 0; a < num_a; a++) {
+ uint32_t ind = group_a[a];
+
+ if (temp_bounds[ind].min.coord[split_axis] > centre.coord[split_axis]) {
+ // add to b
+ group_b[num_b++] = ind;
+
+ // remove from a
+ group_a[a] = group_a[num_a - 1];
+ num_a--;
+
+ // do this one again, as it has been replaced
+ a--;
+ }
+ }
+
+ // detect when split on longest axis failed
+ int min_threshold = MAX_ITEMS / 4;
+ int min_group_size[3];
+ min_group_size[0] = MIN(num_a, num_b);
+ if (min_group_size[0] < min_threshold) {
+ // slow but sure .. first move everything back into a
+ for (int b = 0; b < num_b; b++) {
+ group_a[num_a++] = group_b[b];
+ }
+ num_b = 0;
+
+ // now calculate the best split
+ for (int axis = 1; axis < 3; axis++) {
+ split_axis = order[axis];
+ int count = 0;
+
+ for (int a = 0; a < num_a; a++) {
+ uint32_t ind = group_a[a];
+
+ if (temp_bounds[ind].min.coord[split_axis] > centre.coord[split_axis]) {
+ count++;
+ }
+ }
+
+ min_group_size[axis] = MIN(count, num_a - count);
+ } // for axis
+
+ // best axis
+ int best_axis = 0;
+ int best_min = min_group_size[0];
+ for (int axis = 1; axis < 3; axis++) {
+ if (min_group_size[axis] > best_min) {
+ best_min = min_group_size[axis];
+ best_axis = axis;
+ }
+ }
+
+ // now finally do the split
+ if (best_min > 0) {
+ split_axis = order[best_axis];
+
+ for (int a = 0; a < num_a; a++) {
+ uint32_t ind = group_a[a];
+
+ if (temp_bounds[ind].min.coord[split_axis] > centre.coord[split_axis]) {
+ // add to b
+ group_b[num_b++] = ind;
+
+ // remove from a
+ group_a[a] = group_a[num_a - 1];
+ num_a--;
+
+ // do this one again, as it has been replaced
+ a--;
+ }
+ }
+ } // if there was a split!
+ } // if the longest axis wasn't a good split
+
+ // special case, none crossed threshold
+ if (!num_b) {
+ uint32_t ind = group_a[0];
+
+ // add to b
+ group_b[num_b++] = ind;
+
+ // remove from a
+ group_a[0] = group_a[num_a - 1];
+ num_a--;
+ }
+ // opposite problem! :)
+ if (!num_a) {
+ uint32_t ind = group_b[0];
+
+ // add to a
+ group_a[num_a++] = ind;
+
+ // remove from b
+ group_b[0] = group_b[num_b - 1];
+ num_b--;
+ }
+}
+
+void _split_leaf_sort_groups(int &num_a, int &num_b, uint16_t *group_a, uint16_t *group_b, const BVHABB_CLASS *temp_bounds) {
+ BVHABB_CLASS groupb_aabb;
+ groupb_aabb.set_to_max_opposite_extents();
+ for (int n = 0; n < num_b; n++) {
+ int which = group_b[n];
+ groupb_aabb.merge(temp_bounds[which]);
+ }
+ BVHABB_CLASS groupb_aabb_new;
+
+ BVHABB_CLASS rest_aabb;
+
+ float best_size = FLT_MAX;
+ int best_candidate = -1;
+
+ // find most likely from a to move into b
+ for (int check = 0; check < num_a; check++) {
+ rest_aabb.set_to_max_opposite_extents();
+ groupb_aabb_new = groupb_aabb;
+
+ // find aabb of all the rest
+ for (int rest = 0; rest < num_a; rest++) {
+ if (rest == check) {
+ continue;
+ }
+
+ int which = group_a[rest];
+ rest_aabb.merge(temp_bounds[which]);
+ }
+
+ groupb_aabb_new.merge(temp_bounds[group_a[check]]);
+
+ // now compare the sizes
+ float size = groupb_aabb_new.get_area() + rest_aabb.get_area();
+ if (size < best_size) {
+ best_size = size;
+ best_candidate = check;
+ }
+ }
+
+ // we should now have the best, move it from group a to group b
+ group_b[num_b++] = group_a[best_candidate];
+
+ // remove best candidate from group a
+ num_a--;
+ group_a[best_candidate] = group_a[num_a];
+}
+
+uint32_t split_leaf(uint32_t p_node_id, const BVHABB_CLASS &p_added_item_aabb) {
+ return split_leaf_complex(p_node_id, p_added_item_aabb);
+}
+
+// aabb is the new inserted node
+uint32_t split_leaf_complex(uint32_t p_node_id, const BVHABB_CLASS &p_added_item_aabb) {
+ VERBOSE_PRINT("split_leaf");
+
+ // note the tnode before and AFTER splitting may be a different address
+ // in memory because the vector could get relocated. So we need to reget
+ // the tnode after the split
+ BVH_ASSERT(_nodes[p_node_id].is_leaf());
+
+ // first create child leaf nodes
+ uint32_t *child_ids = (uint32_t *)alloca(sizeof(uint32_t) * MAX_CHILDREN);
+
+ for (int n = 0; n < MAX_CHILDREN; n++) {
+ // create node children
+ TNode *child_node = _nodes.request(child_ids[n]);
+
+ child_node->clear();
+
+ // back link to parent
+ child_node->parent_id = p_node_id;
+
+ // make each child a leaf node
+ node_make_leaf(child_ids[n]);
+ }
+
+ // don't get any leaves or nodes till AFTER the split
+ TNode &tnode = _nodes[p_node_id];
+ uint32_t orig_leaf_id = tnode.get_leaf_id();
+ const TLeaf &orig_leaf = _node_get_leaf(tnode);
+
+ // store the final child ids
+ for (int n = 0; n < MAX_CHILDREN; n++) {
+ tnode.children[n] = child_ids[n];
+ }
+
+ // mark as no longer a leaf node
+ tnode.num_children = MAX_CHILDREN;
+
+ // 2 groups, A and B, and assign children to each to split equally
+ int max_children = orig_leaf.num_items + 1; // plus 1 for the wildcard .. the item being added
+ //CRASH_COND(max_children > MAX_CHILDREN);
+
+ uint16_t *group_a = (uint16_t *)alloca(sizeof(uint16_t) * max_children);
+ uint16_t *group_b = (uint16_t *)alloca(sizeof(uint16_t) * max_children);
+
+ // we are copying the ABBs. This is ugly, but we need one extra for the inserted item...
+ BVHABB_CLASS *temp_bounds = (BVHABB_CLASS *)alloca(sizeof(BVHABB_CLASS) * max_children);
+
+ int num_a = max_children;
+ int num_b = 0;
+
+ // setup - start with all in group a
+ for (int n = 0; n < orig_leaf.num_items; n++) {
+ group_a[n] = n;
+ temp_bounds[n] = orig_leaf.get_aabb(n);
+ }
+ // wildcard
+ int wildcard = orig_leaf.num_items;
+
+ group_a[wildcard] = wildcard;
+ temp_bounds[wildcard] = p_added_item_aabb;
+
+ // we can choose here either an equal split, or just 1 in the new leaf
+ _split_leaf_sort_groups_simple(num_a, num_b, group_a, group_b, temp_bounds, tnode.aabb);
+
+ uint32_t wildcard_node = BVHCommon::INVALID;
+
+ // now there should be equal numbers in both groups
+ for (int n = 0; n < num_a; n++) {
+ int which = group_a[n];
+
+ if (which != wildcard) {
+ const BVHABB_CLASS &source_item_aabb = orig_leaf.get_aabb(which);
+ uint32_t source_item_ref_id = orig_leaf.get_item_ref_id(which);
+ //const Item &source_item = orig_leaf.get_item(which);
+ _node_add_item(tnode.children[0], source_item_ref_id, source_item_aabb);
+ } else {
+ wildcard_node = tnode.children[0];
+ }
+ }
+ for (int n = 0; n < num_b; n++) {
+ int which = group_b[n];
+
+ if (which != wildcard) {
+ const BVHABB_CLASS &source_item_aabb = orig_leaf.get_aabb(which);
+ uint32_t source_item_ref_id = orig_leaf.get_item_ref_id(which);
+ //const Item &source_item = orig_leaf.get_item(which);
+ _node_add_item(tnode.children[1], source_item_ref_id, source_item_aabb);
+ } else {
+ wildcard_node = tnode.children[1];
+ }
+ }
+
+ // now remove all items from the parent and replace with the child nodes
+ _leaves.free(orig_leaf_id);
+
+ // we should keep the references up to date!
+ for (int n = 0; n < MAX_CHILDREN; n++) {
+ _split_inform_references(tnode.children[n]);
+ }
+
+ refit_upward(p_node_id);
+
+ BVH_ASSERT(wildcard_node != BVHCommon::INVALID);
+ return wildcard_node;
+}
diff --git a/core/math/bvh_structs.inc b/core/math/bvh_structs.inc
new file mode 100644
index 0000000000..4133ba6c10
--- /dev/null
+++ b/core/math/bvh_structs.inc
@@ -0,0 +1,181 @@
+
+public:
+struct ItemRef {
+ uint32_t tnode_id; // -1 is invalid
+ uint32_t item_id; // in the leaf
+
+ bool is_active() const { return tnode_id != BVHCommon::INACTIVE; }
+ void set_inactive() {
+ tnode_id = BVHCommon::INACTIVE;
+ item_id = BVHCommon::INACTIVE;
+ }
+};
+
+// extra info kept in separate parallel list to the references,
+// as this is less used as keeps cache better
+struct ItemExtra {
+ uint32_t last_updated_tick;
+ uint32_t pairable;
+ uint32_t pairable_mask;
+ uint32_t pairable_type;
+
+ int32_t subindex;
+
+ // the active reference is a separate list of which references
+ // are active so that we can slowly iterate through it over many frames for
+ // slow optimize.
+ uint32_t active_ref_id;
+
+ T *userdata;
+};
+
+// this is an item OR a child node depending on whether a leaf node
+struct Item {
+ BVHABB_CLASS aabb;
+ uint32_t item_ref_id;
+};
+
+// tree leaf
+struct TLeaf {
+ uint16_t num_items;
+
+private:
+ uint16_t dirty;
+ // separate data orientated lists for faster SIMD traversal
+ uint32_t item_ref_ids[MAX_ITEMS];
+ BVHABB_CLASS aabbs[MAX_ITEMS];
+
+public:
+ // accessors
+ BVHABB_CLASS &get_aabb(uint32_t p_id) { return aabbs[p_id]; }
+ const BVHABB_CLASS &get_aabb(uint32_t p_id) const { return aabbs[p_id]; }
+
+ uint32_t &get_item_ref_id(uint32_t p_id) { return item_ref_ids[p_id]; }
+ const uint32_t &get_item_ref_id(uint32_t p_id) const { return item_ref_ids[p_id]; }
+
+ bool is_dirty() const { return dirty; }
+ void set_dirty(bool p) { dirty = p; }
+
+ void clear() {
+ num_items = 0;
+ set_dirty(true);
+ }
+ bool is_full() const { return num_items >= MAX_ITEMS; }
+
+ void remove_item_unordered(uint32_t p_id) {
+ BVH_ASSERT(p_id < num_items);
+ num_items--;
+ aabbs[p_id] = aabbs[num_items];
+ item_ref_ids[p_id] = item_ref_ids[num_items];
+ }
+
+ uint32_t request_item() {
+ if (num_items < MAX_ITEMS) {
+ uint32_t id = num_items;
+ num_items++;
+ return id;
+ }
+ return -1;
+ }
+};
+
+// tree node
+struct TNode {
+ BVHABB_CLASS aabb;
+ // either number of children if positive
+ // or leaf id if negative (leaf id 0 is disallowed)
+ union {
+ int32_t num_children;
+ int32_t neg_leaf_id;
+ };
+ uint32_t parent_id; // or -1
+ uint16_t children[MAX_CHILDREN];
+
+ // height in the tree, where leaves are 0, and all above are 1+
+ // (or the highest where there is a tie off)
+ int32_t height;
+
+ bool is_leaf() const { return num_children < 0; }
+ void set_leaf_id(int id) { neg_leaf_id = -id; }
+ int get_leaf_id() const { return -neg_leaf_id; }
+
+ void clear() {
+ num_children = 0;
+ parent_id = BVHCommon::INVALID;
+ height = 0; // or -1 for testing
+
+ // for safety set to improbable value
+ aabb.set_to_max_opposite_extents();
+
+ // other members are not blanked for speed .. they may be uninitialized
+ }
+
+ bool is_full_of_children() const { return num_children >= MAX_CHILDREN; }
+
+ void remove_child_internal(uint32_t child_num) {
+ children[child_num] = children[num_children - 1];
+ num_children--;
+ }
+
+ int find_child(uint32_t p_child_node_id) {
+ BVH_ASSERT(!is_leaf());
+
+ for (int n = 0; n < num_children; n++) {
+ if (children[n] == p_child_node_id) {
+ return n;
+ }
+ }
+
+ // not found
+ return -1;
+ }
+};
+
+// instead of using linked list we maintain
+// item references (for quick lookup)
+PooledList<ItemRef, true> _refs;
+PooledList<ItemExtra, true> _extra;
+PooledList<ItemPairs> _pairs;
+
+// these 2 are not in sync .. nodes != leaves!
+PooledList<TNode, true> _nodes;
+PooledList<TLeaf, true> _leaves;
+
+// we can maintain an un-ordered list of which references are active,
+// in order to do a slow incremental optimize of the tree over each frame.
+// This will work best if dynamic objects and static objects are in a different tree.
+LocalVector<uint32_t, uint32_t, true> _active_refs;
+uint32_t _current_active_ref = 0;
+
+// instead of translating directly to the userdata output,
+// we keep an intermediate list of hits as reference IDs, which can be used
+// for pairing collision detection
+LocalVector<uint32_t, uint32_t, true> _cull_hits;
+
+// we now have multiple root nodes, allowing us to store
+// more than 1 tree. This can be more efficient, while sharing the same
+// common lists
+enum { NUM_TREES = 2,
+};
+
+// Tree 0 - Non pairable
+// Tree 1 - Pairable
+// This is more efficient because in physics we only need check non pairable against the pairable tree.
+uint32_t _root_node_id[NUM_TREES];
+int _current_tree = 0;
+
+// these values may need tweaking according to the project
+// the bound of the world, and the average velocities of the objects
+
+// node expansion is important in the rendering tree
+// larger values give less re-insertion as items move...
+// but on the other hand over estimates the bounding box of nodes.
+// we can either use auto mode, where the expansion is based on the root node size, or specify manually
+real_t _node_expansion = 0.5;
+bool _auto_node_expansion = true;
+
+// pairing expansion important for physics pairing
+// larger values gives more 'sticky' pairing, and is less likely to exhibit tunneling
+// we can either use auto mode, where the expansion is based on the root node size, or specify manually
+real_t _pairing_expansion = 0.1;
+bool _auto_pairing_expansion = true;
diff --git a/core/math/bvh_tree.h b/core/math/bvh_tree.h
new file mode 100644
index 0000000000..64c5f6e254
--- /dev/null
+++ b/core/math/bvh_tree.h
@@ -0,0 +1,422 @@
+/*************************************************************************/
+/* bvh_tree.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 BVH_TREE_H
+#define BVH_TREE_H
+
+// BVH Tree
+// This is an implementation of a dynamic BVH with templated leaf size.
+// This differs from most dynamic BVH in that it can handle more than 1 object
+// in leaf nodes. This can make it far more efficient in certain circumstances.
+// It also means that the splitting logic etc have to be completely different
+// to a simpler tree.
+// Note that MAX_CHILDREN should be fixed at 2 for now.
+
+#include "core/math/aabb.h"
+#include "core/math/bvh_abb.h"
+#include "core/math/geometry_3d.h"
+#include "core/math/vector3.h"
+#include "core/string/print_string.h"
+#include "core/templates/local_vector.h"
+#include "core/templates/pooled_list.h"
+#include <limits.h>
+
+#define BVHABB_CLASS BVH_ABB<Bounds, Point>
+
+// never do these checks in release
+#if defined(TOOLS_ENABLED) && defined(DEBUG_ENABLED)
+//#define BVH_VERBOSE
+//#define BVH_VERBOSE_TREE
+
+//#define BVH_VERBOSE_FRAME
+//#define BVH_CHECKS
+//#define BVH_INTEGRITY_CHECKS
+#endif
+
+// debug only assert
+#ifdef BVH_CHECKS
+#define BVH_ASSERT(a) CRASH_COND((a) == false)
+#else
+#define BVH_ASSERT(a)
+#endif
+
+#ifdef BVH_VERBOSE
+#define VERBOSE_PRINT print_line
+#else
+#define VERBOSE_PRINT(a)
+#endif
+
+// really just a namespace
+struct BVHCommon {
+ // these could possibly also be the same constant,
+ // although this may be useful for debugging.
+ // or use zero for invalid and +1 based indices.
+ static const uint32_t INVALID = (0xffffffff);
+ static const uint32_t INACTIVE = (0xfffffffe);
+};
+
+// really a handle, can be anything
+// note that zero is a valid reference for the BVH .. this may involve using
+// a plus one based ID for clients that expect 0 to be invalid.
+struct BVHHandle {
+ // conversion operator
+ operator uint32_t() const { return _data; }
+ void set(uint32_t p_value) { _data = p_value; }
+
+ uint32_t _data;
+
+ void set_invalid() { _data = BVHCommon::INVALID; }
+ bool is_invalid() const { return _data == BVHCommon::INVALID; }
+ uint32_t id() const { return _data; }
+ void set_id(uint32_t p_id) { _data = p_id; }
+
+ bool operator==(const BVHHandle &p_h) const { return _data == p_h._data; }
+ bool operator!=(const BVHHandle &p_h) const { return (*this == p_h) == false; }
+};
+
+// helper class to make iterative versions of recursive functions
+template <class T>
+class BVH_IterativeInfo {
+public:
+ enum {
+ ALLOCA_STACK_SIZE = 128
+ };
+
+ int32_t depth = 1;
+ int32_t threshold = ALLOCA_STACK_SIZE - 2;
+ T *stack;
+ //only used in rare occasions when you run out of alloca memory
+ // because tree is too unbalanced.
+ LocalVector<T> aux_stack;
+ int32_t get_alloca_stacksize() const { return ALLOCA_STACK_SIZE * sizeof(T); }
+
+ T *get_first() const {
+ return &stack[0];
+ }
+
+ // pop the last member of the stack, or return false
+ bool pop(T &r_value) {
+ if (!depth) {
+ return false;
+ }
+
+ depth--;
+ r_value = stack[depth];
+ return true;
+ }
+
+ // request new addition to stack
+ T *request() {
+ if (depth > threshold) {
+ if (aux_stack.is_empty()) {
+ aux_stack.resize(ALLOCA_STACK_SIZE * 2);
+ memcpy(aux_stack.ptr(), stack, get_alloca_stacksize());
+ } else {
+ aux_stack.resize(aux_stack.size() * 2);
+ }
+ stack = aux_stack.ptr();
+ threshold = aux_stack.size() - 2;
+ }
+ return &stack[depth++];
+ }
+};
+
+template <class T, int MAX_CHILDREN, int MAX_ITEMS, bool USE_PAIRS = false, class Bounds = AABB, class Point = Vector3>
+class BVH_Tree {
+ friend class BVH;
+
+#include "bvh_pair.inc"
+#include "bvh_structs.inc"
+
+public:
+ BVH_Tree() {
+ for (int n = 0; n < NUM_TREES; n++) {
+ _root_node_id[n] = BVHCommon::INVALID;
+ }
+
+ // disallow zero leaf ids
+ // (as these ids are stored as negative numbers in the node)
+ uint32_t dummy_leaf_id;
+ _leaves.request(dummy_leaf_id);
+ }
+
+private:
+ bool node_add_child(uint32_t p_node_id, uint32_t p_child_node_id) {
+ TNode &tnode = _nodes[p_node_id];
+ if (tnode.is_full_of_children()) {
+ return false;
+ }
+
+ tnode.children[tnode.num_children] = p_child_node_id;
+ tnode.num_children += 1;
+
+ // back link in the child to the parent
+ TNode &tnode_child = _nodes[p_child_node_id];
+ tnode_child.parent_id = p_node_id;
+
+ return true;
+ }
+
+ void node_replace_child(uint32_t p_parent_id, uint32_t p_old_child_id, uint32_t p_new_child_id) {
+ TNode &parent = _nodes[p_parent_id];
+ BVH_ASSERT(!parent.is_leaf());
+
+ int child_num = parent.find_child(p_old_child_id);
+ BVH_ASSERT(child_num != BVHCommon::INVALID);
+ parent.children[child_num] = p_new_child_id;
+
+ TNode &new_child = _nodes[p_new_child_id];
+ new_child.parent_id = p_parent_id;
+ }
+
+ void node_remove_child(uint32_t p_parent_id, uint32_t p_child_id, bool p_prevent_sibling = false) {
+ TNode &parent = _nodes[p_parent_id];
+ BVH_ASSERT(!parent.is_leaf());
+
+ int child_num = parent.find_child(p_child_id);
+ BVH_ASSERT(child_num != BVHCommon::INVALID);
+
+ parent.remove_child_internal(child_num);
+
+ // no need to keep back references for children at the moment
+
+ uint32_t sibling_id; // always a node id, as tnode is never a leaf
+ bool sibling_present = false;
+
+ // if there are more children, or this is the root node, don't try and delete
+ if (parent.num_children > 1) {
+ return;
+ }
+
+ // if there is 1 sibling, it can be moved to be a child of the
+ if (parent.num_children == 1) {
+ // else there is now a redundant node with one child, which can be removed
+ sibling_id = parent.children[0];
+ sibling_present = true;
+ }
+
+ // now there may be no children in this node .. in which case it can be deleted
+ // remove node if empty
+ // remove link from parent
+ uint32_t grandparent_id = parent.parent_id;
+
+ // special case for root node
+ if (grandparent_id == BVHCommon::INVALID) {
+ if (sibling_present) {
+ // change the root node
+ change_root_node(sibling_id);
+
+ // delete the old root node as no longer needed
+ _nodes.free(p_parent_id);
+ }
+
+ return;
+ }
+
+ if (sibling_present) {
+ node_replace_child(grandparent_id, p_parent_id, sibling_id);
+ } else {
+ node_remove_child(grandparent_id, p_parent_id, true);
+ }
+
+ // put the node on the free list to recycle
+ _nodes.free(p_parent_id);
+ }
+
+ // this relies on _current_tree being accurate
+ void change_root_node(uint32_t p_new_root_id) {
+ _root_node_id[_current_tree] = p_new_root_id;
+ TNode &root = _nodes[p_new_root_id];
+
+ // mark no parent
+ root.parent_id = BVHCommon::INVALID;
+ }
+
+ void node_make_leaf(uint32_t p_node_id) {
+ uint32_t child_leaf_id;
+ TLeaf *child_leaf = _leaves.request(child_leaf_id);
+ child_leaf->clear();
+
+ // zero is reserved at startup, to prevent this id being used
+ // (as they are stored as negative values in the node, and zero is already taken)
+ BVH_ASSERT(child_leaf_id != 0);
+
+ TNode &node = _nodes[p_node_id];
+ node.neg_leaf_id = -(int)child_leaf_id;
+ }
+
+ void node_remove_item(uint32_t p_ref_id, BVHABB_CLASS *r_old_aabb = nullptr) {
+ // get the reference
+ ItemRef &ref = _refs[p_ref_id];
+ uint32_t owner_node_id = ref.tnode_id;
+
+ // debug draw special
+ // This may not be needed
+ if (owner_node_id == BVHCommon::INVALID) {
+ return;
+ }
+
+ TNode &tnode = _nodes[owner_node_id];
+ CRASH_COND(!tnode.is_leaf());
+
+ TLeaf &leaf = _node_get_leaf(tnode);
+
+ // if the aabb is not determining the corner size, then there is no need to refit!
+ // (optimization, as merging AABBs takes a lot of time)
+ const BVHABB_CLASS &old_aabb = leaf.get_aabb(ref.item_id);
+
+ // shrink a little to prevent using corner aabbs
+ // in order to miss the corners first we shrink by node_expansion
+ // (which is added to the overall bound of the leaf), then we also
+ // shrink by an epsilon, in order to miss out the very corner aabbs
+ // which are important in determining the bound. Any other aabb
+ // within this can be removed and not affect the overall bound.
+ BVHABB_CLASS node_bound = tnode.aabb;
+ node_bound.expand(-_node_expansion - 0.001f);
+ bool refit = true;
+
+ if (node_bound.is_other_within(old_aabb)) {
+ refit = false;
+ }
+
+ // record the old aabb if required (for incremental remove_and_reinsert)
+ if (r_old_aabb) {
+ *r_old_aabb = old_aabb;
+ }
+
+ leaf.remove_item_unordered(ref.item_id);
+
+ if (leaf.num_items) {
+ // the swapped item has to have its reference changed to, to point to the new item id
+ uint32_t swapped_ref_id = leaf.get_item_ref_id(ref.item_id);
+
+ ItemRef &swapped_ref = _refs[swapped_ref_id];
+
+ swapped_ref.item_id = ref.item_id;
+
+ // only have to refit if it is an edge item
+ // This is a VERY EXPENSIVE STEP
+ // we defer the refit updates until the update function is called once per frame
+ if (refit) {
+ leaf.set_dirty(true);
+ }
+ } else {
+ // remove node if empty
+ // remove link from parent
+ if (tnode.parent_id != BVHCommon::INVALID) {
+ // DANGER .. this can potentially end up with root node with 1 child ...
+ // we don't want this and must check for it
+
+ uint32_t parent_id = tnode.parent_id;
+
+ node_remove_child(parent_id, owner_node_id);
+ refit_upward(parent_id);
+
+ // put the node on the free list to recycle
+ _nodes.free(owner_node_id);
+ }
+
+ // else if no parent, it is the root node. Do not delete
+ }
+
+ ref.tnode_id = BVHCommon::INVALID;
+ ref.item_id = BVHCommon::INVALID; // unset
+ }
+
+ // returns true if needs refit of PARENT tree only, the node itself AABB is calculated
+ // within this routine
+ bool _node_add_item(uint32_t p_node_id, uint32_t p_ref_id, const BVHABB_CLASS &p_aabb) {
+ ItemRef &ref = _refs[p_ref_id];
+ ref.tnode_id = p_node_id;
+
+ TNode &node = _nodes[p_node_id];
+ BVH_ASSERT(node.is_leaf());
+ TLeaf &leaf = _node_get_leaf(node);
+
+ // optimization - we only need to do a refit
+ // if the added item is changing the AABB of the node.
+ // in most cases it won't.
+ bool needs_refit = true;
+
+ // expand bound now
+ BVHABB_CLASS expanded = p_aabb;
+ expanded.expand(_node_expansion);
+
+ // the bound will only be valid if there is an item in there already
+ if (leaf.num_items) {
+ if (node.aabb.is_other_within(expanded)) {
+ // no change to node AABBs
+ needs_refit = false;
+ } else {
+ node.aabb.merge(expanded);
+ }
+ } else {
+ // bound of the node = the new aabb
+ node.aabb = expanded;
+ }
+
+ ref.item_id = leaf.request_item();
+ BVH_ASSERT(ref.item_id != BVHCommon::INVALID);
+
+ // set the aabb of the new item
+ leaf.get_aabb(ref.item_id) = p_aabb;
+
+ // back reference on the item back to the item reference
+ leaf.get_item_ref_id(ref.item_id) = p_ref_id;
+
+ return needs_refit;
+ }
+
+ uint32_t _node_create_another_child(uint32_t p_node_id, const BVHABB_CLASS &p_aabb) {
+ uint32_t child_node_id;
+ TNode *child_node = _nodes.request(child_node_id);
+ child_node->clear();
+
+ // may not be necessary
+ child_node->aabb = p_aabb;
+
+ node_add_child(p_node_id, child_node_id);
+
+ return child_node_id;
+ }
+
+#include "bvh_cull.inc"
+#include "bvh_debug.inc"
+#include "bvh_integrity.inc"
+#include "bvh_logic.inc"
+#include "bvh_misc.inc"
+#include "bvh_public.inc"
+#include "bvh_refit.inc"
+#include "bvh_split.inc"
+};
+
+#undef VERBOSE_PRINT
+
+#endif // BVH_TREE_H
diff --git a/core/math/geometry_2d.h b/core/math/geometry_2d.h
index 4b5aef352f..4958b5ac6a 100644
--- a/core/math/geometry_2d.h
+++ b/core/math/geometry_2d.h
@@ -395,6 +395,45 @@ public:
H.resize(k);
return H;
}
+
+ static Vector<Point2i> bresenham_line(const Point2i &p_start, const Point2i &p_end) {
+ Vector<Point2i> points;
+
+ Vector2i delta = (p_end - p_start).abs() * 2;
+ Vector2i step = (p_end - p_start).sign();
+ Vector2i current = p_start;
+
+ if (delta.x > delta.y) {
+ int err = delta.x / 2;
+
+ for (; current.x != p_end.x; current.x += step.x) {
+ points.push_back(current);
+
+ err -= delta.y;
+ if (err < 0) {
+ current.y += step.y;
+ err += delta.x;
+ }
+ }
+ } else {
+ int err = delta.y / 2;
+
+ for (; current.y != p_end.y; current.y += step.y) {
+ points.push_back(current);
+
+ err -= delta.x;
+ if (err < 0) {
+ current.x += step.x;
+ err += delta.y;
+ }
+ }
+ }
+
+ points.push_back(current);
+
+ return points;
+ }
+
static Vector<Vector<Vector2>> decompose_polygon_in_convex(Vector<Point2> polygon);
static void make_atlas(const Vector<Size2i> &p_rects, Vector<Point2i> &r_result, Size2i &r_size);
diff --git a/core/math/rect2.h b/core/math/rect2.h
index 512499bdb2..1dc027cf72 100644
--- a/core/math/rect2.h
+++ b/core/math/rect2.h
@@ -182,13 +182,17 @@ struct Rect2 {
inline Rect2 grow(real_t p_amount) const {
Rect2 g = *this;
- g.position.x -= p_amount;
- g.position.y -= p_amount;
- g.size.width += p_amount * 2;
- g.size.height += p_amount * 2;
+ g.grow_by(p_amount);
return g;
}
+ inline void grow_by(real_t p_amount) {
+ position.x -= p_amount;
+ position.y -= p_amount;
+ size.width += p_amount * 2;
+ size.height += p_amount * 2;
+ }
+
inline Rect2 grow_side(Side p_side, real_t p_amount) const {
Rect2 g = *this;
g = g.grow_individual((SIDE_LEFT == p_side) ? p_amount : 0,
diff --git a/core/math/vector2.h b/core/math/vector2.h
index 81bc71d590..6abe0f5ea9 100644
--- a/core/math/vector2.h
+++ b/core/math/vector2.h
@@ -37,18 +37,26 @@
struct Vector2i;
struct Vector2 {
+ static const int AXIS_COUNT = 2;
+
enum Axis {
AXIS_X,
AXIS_Y,
};
union {
- real_t x = 0;
- real_t width;
- };
- union {
- real_t y = 0;
- real_t height;
+ struct {
+ union {
+ real_t x;
+ real_t width;
+ };
+ union {
+ real_t y;
+ real_t height;
+ };
+ };
+
+ real_t coord[2] = { 0 };
};
_FORCE_INLINE_ real_t &operator[](int p_idx) {
@@ -58,6 +66,18 @@ struct Vector2 {
return p_idx ? y : x;
}
+ _FORCE_INLINE_ void set_all(real_t p_value) {
+ x = y = p_value;
+ }
+
+ _FORCE_INLINE_ int min_axis() const {
+ return x < y ? 0 : 1;
+ }
+
+ _FORCE_INLINE_ int max_axis() const {
+ return x < y ? 1 : 0;
+ }
+
void normalize();
Vector2 normalized() const;
bool is_normalized() const;
@@ -280,6 +300,14 @@ struct Vector2i {
return p_idx ? y : x;
}
+ Vector2i min(const Vector2i &p_vector2i) const {
+ return Vector2(MIN(x, p_vector2i.x), MIN(y, p_vector2i.y));
+ }
+
+ Vector2i max(const Vector2i &p_vector2i) const {
+ return Vector2(MAX(x, p_vector2i.x), MAX(y, p_vector2i.y));
+ }
+
Vector2i operator+(const Vector2i &p_v) const;
void operator+=(const Vector2i &p_v);
Vector2i operator-(const Vector2i &p_v) const;
diff --git a/core/math/vector3.cpp b/core/math/vector3.cpp
index f0629d3db8..d4317d506c 100644
--- a/core/math/vector3.cpp
+++ b/core/math/vector3.cpp
@@ -52,14 +52,6 @@ real_t Vector3::get_axis(int p_axis) const {
return operator[](p_axis);
}
-int Vector3::min_axis() const {
- return x < y ? (x < z ? 0 : 2) : (y < z ? 1 : 2);
-}
-
-int Vector3::max_axis() const {
- return x < y ? (y < z ? 2 : 1) : (x < z ? 2 : 0);
-}
-
void Vector3::snap(Vector3 p_step) {
x = Math::snapped(x, p_step.x);
y = Math::snapped(y, p_step.y);
diff --git a/core/math/vector3.h b/core/math/vector3.h
index 377581bb45..b47c3cc916 100644
--- a/core/math/vector3.h
+++ b/core/math/vector3.h
@@ -38,6 +38,8 @@
class Basis;
struct Vector3 {
+ static const int AXIS_COUNT = 3;
+
enum Axis {
AXIS_X,
AXIS_Y,
@@ -65,8 +67,17 @@ struct Vector3 {
void set_axis(int p_axis, real_t p_value);
real_t get_axis(int p_axis) const;
- int min_axis() const;
- int max_axis() const;
+ _FORCE_INLINE_ void set_all(real_t p_value) {
+ x = y = z = p_value;
+ }
+
+ _FORCE_INLINE_ int min_axis() const {
+ return x < y ? (x < z ? 0 : 2) : (y < z ? 1 : 2);
+ }
+
+ _FORCE_INLINE_ int max_axis() const {
+ return x < y ? (y < z ? 2 : 1) : (x < z ? 2 : 0);
+ }
_FORCE_INLINE_ real_t length() const;
_FORCE_INLINE_ real_t length_squared() const;
diff --git a/servers/physics_3d/broad_phase_3d_basic.h b/core/templates/pooled_list.h
index 54d34e005f..b4a6d2d1dd 100644
--- a/servers/physics_3d/broad_phase_3d_basic.h
+++ b/core/templates/pooled_list.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* broad_phase_3d_basic.h */
+/* pooled_list.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,78 +28,68 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef BROAD_PHASE_BASIC_H
-#define BROAD_PHASE_BASIC_H
+#pragma once
-#include "broad_phase_3d_sw.h"
-#include "core/templates/map.h"
+// Simple template to provide a pool with O(1) allocate and free.
+// The freelist could alternatively be a linked list placed within the unused elements
+// to use less memory, however a separate freelist is probably more cache friendly.
-class BroadPhase3DBasic : public BroadPhase3DSW {
- struct Element {
- CollisionObject3DSW *owner;
- bool _static;
- AABB aabb;
- int subindex;
- };
+// NOTE : Take great care when using this with non POD types. The construction and destruction
+// is done in the LocalVector, NOT as part of the pool. So requesting a new item does not guarantee
+// a constructor is run, and free does not guarantee a destructor.
+// You should generally handle clearing
+// an item explicitly after a request, as it may contain 'leftovers'.
+// This is by design for fastest use in the BVH. If you want a more general pool
+// that does call constructors / destructors on request / free, this should probably be
+// a separate template.
- Map<ID, Element> element_map;
+#include "core/templates/local_vector.h"
- ID current;
+template <class T, bool force_trivial = false>
+class PooledList {
+ LocalVector<T, uint32_t, force_trivial> list;
+ LocalVector<uint32_t, uint32_t, true> freelist;
- struct PairKey {
- union {
- struct {
- ID a;
- ID b;
- };
- uint64_t key;
- };
-
- _FORCE_INLINE_ bool operator<(const PairKey &p_key) const {
- return key < p_key.key;
- }
-
- PairKey() { key = 0; }
- PairKey(ID p_a, ID p_b) {
- if (p_a > p_b) {
- a = p_b;
- b = p_a;
- } else {
- a = p_a;
- b = p_b;
- }
- }
- };
-
- Map<PairKey, void *> pair_map;
-
- PairCallback pair_callback;
- void *pair_userdata;
- UnpairCallback unpair_callback;
- void *unpair_userdata;
+ // not all list members are necessarily used
+ int _used_size;
public:
- // 0 is an invalid ID
- virtual ID create(CollisionObject3DSW *p_object, int p_subindex = 0);
- virtual void move(ID p_id, const AABB &p_aabb);
- virtual void set_static(ID p_id, bool p_static);
- virtual void remove(ID p_id);
-
- virtual CollisionObject3DSW *get_object(ID p_id) const;
- virtual bool is_static(ID p_id) const;
- virtual int get_subindex(ID p_id) const;
-
- virtual int cull_point(const Vector3 &p_point, CollisionObject3DSW **p_results, int p_max_results, int *p_result_indices = nullptr);
- virtual int cull_segment(const Vector3 &p_from, const Vector3 &p_to, CollisionObject3DSW **p_results, int p_max_results, int *p_result_indices = nullptr);
- virtual int cull_aabb(const AABB &p_aabb, CollisionObject3DSW **p_results, int p_max_results, int *p_result_indices = nullptr);
-
- virtual void set_pair_callback(PairCallback p_pair_callback, void *p_userdata);
- virtual void set_unpair_callback(UnpairCallback p_unpair_callback, void *p_userdata);
-
- virtual void update();
+ PooledList() {
+ _used_size = 0;
+ }
+
+ int estimate_memory_use() const {
+ return (list.size() * sizeof(T)) + (freelist.size() * sizeof(uint32_t));
+ }
+
+ const T &operator[](uint32_t p_index) const {
+ return list[p_index];
+ }
+ T &operator[](uint32_t p_index) {
+ return list[p_index];
+ }
+
+ int size() const { return _used_size; }
+
+ T *request(uint32_t &r_id) {
+ _used_size++;
+
+ if (freelist.size()) {
+ // pop from freelist
+ int new_size = freelist.size() - 1;
+ r_id = freelist[new_size];
+ freelist.resize(new_size);
+ return &list[r_id];
+ }
- static BroadPhase3DSW *_create();
- BroadPhase3DBasic();
+ r_id = list.size();
+ list.resize(r_id + 1);
+ return &list[r_id];
+ }
+ void free(const uint32_t &p_id) {
+ // should not be on free list already
+ CRASH_COND(p_id >= list.size());
+ freelist.push_back(p_id);
+ _used_size--;
+ }
};
-
-#endif // BROAD_PHASE_BASIC_H
diff --git a/core/variant/callable.h b/core/variant/callable.h
index d91bebfa5f..20d0804292 100644
--- a/core/variant/callable.h
+++ b/core/variant/callable.h
@@ -44,9 +44,9 @@ class CallableCustom;
// is required. It is designed for the standard case (object and method)
// but can be optimized or customized.
+// Enforce 16 bytes with `alignas` to avoid arch-specific alignment issues on x86 vs armv7.
class Callable {
- //needs to be max 16 bytes in 64 bits
- StringName method;
+ alignas(8) StringName method;
union {
uint64_t object = 0;
CallableCustom *custom;
@@ -138,8 +138,9 @@ public:
// be put inside a Variant, but it is not
// used by the engine itself.
+// Enforce 16 bytes with `alignas` to avoid arch-specific alignment issues on x86 vs armv7.
class Signal {
- StringName name;
+ alignas(8) StringName name;
ObjectID object;
public:
diff --git a/doc/classes/AudioEffectPitchShift.xml b/doc/classes/AudioEffectPitchShift.xml
index afe364de63..917556fded 100644
--- a/doc/classes/AudioEffectPitchShift.xml
+++ b/doc/classes/AudioEffectPitchShift.xml
@@ -12,7 +12,7 @@
<methods>
</methods>
<members>
- <member name="fft_size" type="int" setter="set_fft_size" getter="get_fft_size" enum="AudioEffectPitchShift.FFT_Size" default="3">
+ <member name="fft_size" type="int" setter="set_fft_size" getter="get_fft_size" enum="AudioEffectPitchShift.FFTSize" default="3">
</member>
<member name="oversampling" type="int" setter="set_oversampling" getter="get_oversampling" default="4">
</member>
@@ -21,18 +21,18 @@
</member>
</members>
<constants>
- <constant name="FFT_SIZE_256" value="0" enum="FFT_Size">
+ <constant name="FFT_SIZE_256" value="0" enum="FFTSize">
</constant>
- <constant name="FFT_SIZE_512" value="1" enum="FFT_Size">
+ <constant name="FFT_SIZE_512" value="1" enum="FFTSize">
</constant>
- <constant name="FFT_SIZE_1024" value="2" enum="FFT_Size">
+ <constant name="FFT_SIZE_1024" value="2" enum="FFTSize">
</constant>
- <constant name="FFT_SIZE_2048" value="3" enum="FFT_Size">
+ <constant name="FFT_SIZE_2048" value="3" enum="FFTSize">
</constant>
- <constant name="FFT_SIZE_4096" value="4" enum="FFT_Size">
+ <constant name="FFT_SIZE_4096" value="4" enum="FFTSize">
</constant>
- <constant name="FFT_SIZE_MAX" value="5" enum="FFT_Size">
- Represents the size of the [enum FFT_Size] enum.
+ <constant name="FFT_SIZE_MAX" value="5" enum="FFTSize">
+ Represents the size of the [enum FFTSize] enum.
</constant>
</constants>
</class>
diff --git a/doc/classes/AudioEffectSpectrumAnalyzer.xml b/doc/classes/AudioEffectSpectrumAnalyzer.xml
index 4c08b18f1d..79a8932e25 100644
--- a/doc/classes/AudioEffectSpectrumAnalyzer.xml
+++ b/doc/classes/AudioEffectSpectrumAnalyzer.xml
@@ -11,24 +11,24 @@
<members>
<member name="buffer_length" type="float" setter="set_buffer_length" getter="get_buffer_length" default="2.0">
</member>
- <member name="fft_size" type="int" setter="set_fft_size" getter="get_fft_size" enum="AudioEffectSpectrumAnalyzer.FFT_Size" default="2">
+ <member name="fft_size" type="int" setter="set_fft_size" getter="get_fft_size" enum="AudioEffectSpectrumAnalyzer.FFTSize" default="2">
</member>
<member name="tap_back_pos" type="float" setter="set_tap_back_pos" getter="get_tap_back_pos" default="0.01">
</member>
</members>
<constants>
- <constant name="FFT_SIZE_256" value="0" enum="FFT_Size">
+ <constant name="FFT_SIZE_256" value="0" enum="FFTSize">
</constant>
- <constant name="FFT_SIZE_512" value="1" enum="FFT_Size">
+ <constant name="FFT_SIZE_512" value="1" enum="FFTSize">
</constant>
- <constant name="FFT_SIZE_1024" value="2" enum="FFT_Size">
+ <constant name="FFT_SIZE_1024" value="2" enum="FFTSize">
</constant>
- <constant name="FFT_SIZE_2048" value="3" enum="FFT_Size">
+ <constant name="FFT_SIZE_2048" value="3" enum="FFTSize">
</constant>
- <constant name="FFT_SIZE_4096" value="4" enum="FFT_Size">
+ <constant name="FFT_SIZE_4096" value="4" enum="FFTSize">
</constant>
- <constant name="FFT_SIZE_MAX" value="5" enum="FFT_Size">
- Represents the size of the [enum FFT_Size] enum.
+ <constant name="FFT_SIZE_MAX" value="5" enum="FFTSize">
+ Represents the size of the [enum FFTSize] enum.
</constant>
</constants>
</class>
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 91e90d051d..0c9df071a7 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -469,46 +469,6 @@
<description>
</description>
</method>
- <method name="native_video_is_playing" qualifiers="const">
- <return type="bool">
- </return>
- <description>
- </description>
- </method>
- <method name="native_video_pause">
- <return type="void">
- </return>
- <description>
- </description>
- </method>
- <method name="native_video_play">
- <return type="int" enum="Error">
- </return>
- <argument index="0" name="path" type="String">
- </argument>
- <argument index="1" name="volume" type="float">
- </argument>
- <argument index="2" name="audio_track" type="String">
- </argument>
- <argument index="3" name="subtitle_track" type="String">
- </argument>
- <argument index="4" name="screen" type="int">
- </argument>
- <description>
- </description>
- </method>
- <method name="native_video_stop">
- <return type="void">
- </return>
- <description>
- </description>
- </method>
- <method name="native_video_unpause">
- <return type="void">
- </return>
- <description>
- </description>
- </method>
<method name="process_events">
<return type="void">
</return>
@@ -1065,25 +1025,23 @@
</constant>
<constant name="FEATURE_CUSTOM_CURSOR_SHAPE" value="8" enum="Feature">
</constant>
- <constant name="FEATURE_NATIVE_VIDEO" value="9" enum="Feature">
- </constant>
- <constant name="FEATURE_NATIVE_DIALOG" value="10" enum="Feature">
+ <constant name="FEATURE_NATIVE_DIALOG" value="9" enum="Feature">
</constant>
- <constant name="FEATURE_CONSOLE_WINDOW" value="11" enum="Feature">
+ <constant name="FEATURE_CONSOLE_WINDOW" value="10" enum="Feature">
</constant>
- <constant name="FEATURE_IME" value="12" enum="Feature">
+ <constant name="FEATURE_IME" value="11" enum="Feature">
</constant>
- <constant name="FEATURE_WINDOW_TRANSPARENCY" value="13" enum="Feature">
+ <constant name="FEATURE_WINDOW_TRANSPARENCY" value="12" enum="Feature">
</constant>
- <constant name="FEATURE_HIDPI" value="14" enum="Feature">
+ <constant name="FEATURE_HIDPI" value="13" enum="Feature">
</constant>
- <constant name="FEATURE_ICON" value="15" enum="Feature">
+ <constant name="FEATURE_ICON" value="14" enum="Feature">
</constant>
- <constant name="FEATURE_NATIVE_ICON" value="16" enum="Feature">
+ <constant name="FEATURE_NATIVE_ICON" value="15" enum="Feature">
</constant>
- <constant name="FEATURE_ORIENTATION" value="17" enum="Feature">
+ <constant name="FEATURE_ORIENTATION" value="16" enum="Feature">
</constant>
- <constant name="FEATURE_SWAP_BUFFERS" value="18" enum="Feature">
+ <constant name="FEATURE_SWAP_BUFFERS" value="17" enum="Feature">
</constant>
<constant name="MOUSE_MODE_VISIBLE" value="0" enum="MouseMode">
</constant>
diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml
index d997073849..cb95deb9a0 100644
--- a/doc/classes/OS.xml
+++ b/doc/classes/OS.xml
@@ -348,6 +348,7 @@
</return>
<description>
Returns a string that is unique to the device.
+ [b]Note:[/b] This string may change without notice if the user reinstalls/upgrades their operating system or changes their hardware. This means it should generally not be used to encrypt persistent data as the data saved prior to an unexpected ID change would become inaccessible. The returned string may also be falsified using external programs, so do not rely on the string returned by [method get_unique_id] for security purposes.
[b]Note:[/b] Returns an empty string on HTML5 and UWP, as this method isn't implemented on those platforms yet.
</description>
</method>
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 3200f789b8..d38c3fc0d8 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -1197,12 +1197,6 @@
The CA certificates bundle to use for SSL connections. If this is set to a non-empty value, this will [i]override[/i] Godot's default [url=https://github.com/godotengine/godot/blob/master/thirdparty/certs/ca-certificates.crt]Mozilla certificate bundle[/url]. If left empty, the default certificate bundle will be used.
If in doubt, leave this setting empty.
</member>
- <member name="physics/2d/bp_hash_table_size" type="int" setter="" getter="" default="4096">
- Size of the hash table used for the broad-phase 2D hash grid algorithm.
- </member>
- <member name="physics/2d/cell_size" type="int" setter="" getter="" default="128">
- Cell size used for the broad-phase 2D hash grid algorithm (in pixels).
- </member>
<member name="physics/2d/default_angular_damp" type="float" setter="" getter="" default="1.0">
The default angular damp in 2D.
[b]Note:[/b] Good values are in the range [code]0[/code] to [code]1[/code]. At value [code]0[/code] objects will keep moving with the same velocity. Values greater than [code]1[/code] will aim to reduce the velocity to [code]0[/code] in less than a second e.g. a value of [code]2[/code] will aim to reduce the velocity to [code]0[/code] in half a second. A value equal to or greater than the physics frame rate ([member ProjectSettings.physics/common/physics_fps], [code]60[/code] by default) will bring the object to a stop in one iteration.
@@ -1239,9 +1233,6 @@
The default linear damp in 2D.
[b]Note:[/b] Good values are in the range [code]0[/code] to [code]1[/code]. At value [code]0[/code] objects will keep moving with the same velocity. Values greater than [code]1[/code] will aim to reduce the velocity to [code]0[/code] in less than a second e.g. a value of [code]2[/code] will aim to reduce the velocity to [code]0[/code] in half a second. A value equal to or greater than the physics frame rate ([member ProjectSettings.physics/common/physics_fps], [code]60[/code] by default) will bring the object to a stop in one iteration.
</member>
- <member name="physics/2d/large_object_surface_threshold_in_cells" type="int" setter="" getter="" default="512">
- Threshold defining the surface size that constitutes a large object with regard to cells in the broad-phase 2D hash grid algorithm.
- </member>
<member name="physics/2d/physics_engine" type="String" setter="" getter="" default="&quot;DEFAULT&quot;">
Sets which physics engine to use for 2D physics.
"DEFAULT" and "GodotPhysics2D" are the same, as there is currently no alternative 2D physics server implemented.
diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml
index c6dd6fb142..0b278d7d25 100644
--- a/doc/classes/Skeleton3D.xml
+++ b/doc/classes/Skeleton3D.xml
@@ -91,6 +91,15 @@
Returns the overall transform of the specified bone, with respect to the skeleton. Being relative to the skeleton frame, this is not the actual "global" transform of the bone.
</description>
</method>
+ <method name="get_bone_global_pose_no_override" qualifiers="const">
+ <return type="Transform">
+ </return>
+ <argument index="0" name="bone_idx" type="int">
+ </argument>
+ <description>
+ Returns the overall transform of the specified bone, with respect to the skeleton, but without any global pose overrides. Being relative to the skeleton frame, this is not the actual "global" transform of the bone.
+ </description>
+ </method>
<method name="get_bone_name" qualifiers="const">
<return type="String">
</return>
diff --git a/doc/classes/TileData.xml b/doc/classes/TileData.xml
new file mode 100644
index 0000000000..e3bc910ab6
--- /dev/null
+++ b/doc/classes/TileData.xml
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="TileData" inherits="Object" version="4.0">
+ <brief_description>
+ </brief_description>
+ <description>
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="add_collision_shape">
+ <return type="void">
+ </return>
+ <argument index="0" name="layer_id" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="get_collision_shape_one_way_margin" qualifiers="const">
+ <return type="float">
+ </return>
+ <argument index="0" name="layer_id" type="int">
+ </argument>
+ <argument index="1" name="shape_index" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="get_collision_shape_shape" qualifiers="const">
+ <return type="Shape2D">
+ </return>
+ <argument index="0" name="layer_id" type="int">
+ </argument>
+ <argument index="1" name="shape_index" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="get_collision_shapes_count" qualifiers="const">
+ <return type="int">
+ </return>
+ <argument index="0" name="layer_id" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="get_custom_data" qualifiers="const">
+ <return type="Variant">
+ </return>
+ <argument index="0" name="layer_name" type="String">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="get_custom_data_by_layer_id" qualifiers="const">
+ <return type="Variant">
+ </return>
+ <argument index="0" name="layer_id" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="get_navigation_polygon" qualifiers="const">
+ <return type="NavigationPolygon">
+ </return>
+ <argument index="0" name="layer_id" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="get_occluder" qualifiers="const">
+ <return type="OccluderPolygon2D">
+ </return>
+ <argument index="0" name="layer_id" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="get_peering_bit_terrain" qualifiers="const">
+ <return type="int">
+ </return>
+ <argument index="0" name="peering_bit" type="int" enum="TileSet.CellNeighbor">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="is_collision_shape_one_way" qualifiers="const">
+ <return type="bool">
+ </return>
+ <argument index="0" name="layer_id" type="int">
+ </argument>
+ <argument index="1" name="shape_index" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="remove_collision_shape">
+ <return type="void">
+ </return>
+ <argument index="0" name="layer_id" type="int">
+ </argument>
+ <argument index="1" name="shape_index" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="set_collision_shape_one_way">
+ <return type="void">
+ </return>
+ <argument index="0" name="layer_id" type="int">
+ </argument>
+ <argument index="1" name="shape_index" type="int">
+ </argument>
+ <argument index="2" name="one_way" type="bool">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="set_collision_shape_one_way_margin">
+ <return type="void">
+ </return>
+ <argument index="0" name="layer_id" type="int">
+ </argument>
+ <argument index="1" name="shape_index" type="int">
+ </argument>
+ <argument index="2" name="one_way_margin" type="float">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="set_collision_shape_shape">
+ <return type="void">
+ </return>
+ <argument index="0" name="layer_id" type="int">
+ </argument>
+ <argument index="1" name="shape_index" type="int">
+ </argument>
+ <argument index="2" name="shape" type="Shape2D">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="set_collision_shapes_count">
+ <return type="void">
+ </return>
+ <argument index="0" name="layer_id" type="int">
+ </argument>
+ <argument index="1" name="shapes_count" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="set_custom_data">
+ <return type="void">
+ </return>
+ <argument index="0" name="layer_name" type="String">
+ </argument>
+ <argument index="1" name="value" type="Variant">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="set_custom_data_by_layer_id">
+ <return type="void">
+ </return>
+ <argument index="0" name="layer_id" type="int">
+ </argument>
+ <argument index="1" name="value" type="Variant">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="set_navigation_polygon">
+ <return type="void">
+ </return>
+ <argument index="0" name="layer_id" type="int">
+ </argument>
+ <argument index="1" name="navigation_polygon" type="NavigationPolygon">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="set_occluder">
+ <return type="void">
+ </return>
+ <argument index="0" name="layer_id" type="int">
+ </argument>
+ <argument index="1" name="occluder_polygon" type="OccluderPolygon2D">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="set_peering_bit_terrain">
+ <return type="void">
+ </return>
+ <argument index="0" name="peering_bit" type="int" enum="TileSet.CellNeighbor">
+ </argument>
+ <argument index="1" name="terrain" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="tile_get_material" qualifiers="const">
+ <return type="ShaderMaterial">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="tile_set_material">
+ <return type="void">
+ </return>
+ <argument index="0" name="material" type="ShaderMaterial">
+ </argument>
+ <description>
+ </description>
+ </method>
+ </methods>
+ <members>
+ <member name="flip_h" type="bool" setter="set_flip_h" getter="get_flip_h" default="false">
+ </member>
+ <member name="flip_v" type="bool" setter="set_flip_v" getter="get_flip_v" default="false">
+ </member>
+ <member name="modulate" type="Color" setter="set_modulate" getter="get_modulate" default="Color( 1, 1, 1, 1 )">
+ </member>
+ <member name="probability" type="float" setter="set_probability" getter="get_probability" default="1.0">
+ </member>
+ <member name="terrain_set" type="int" setter="set_terrain_set" getter="get_terrain_set" default="-1">
+ </member>
+ <member name="texture_offset" type="Vector2i" setter="set_texture_offset" getter="get_texture_offset" default="Vector2i( 0, 0 )">
+ </member>
+ <member name="transpose" type="bool" setter="set_transpose" getter="get_transpose" default="false">
+ </member>
+ <member name="y_sort_origin" type="Vector2i" setter="set_y_sort_origin" getter="get_y_sort_origin" default="Vector2i( 0, 0 )">
+ </member>
+ <member name="z_index" type="int" setter="set_z_index" getter="get_z_index" default="0">
+ </member>
+ </members>
+ <signals>
+ <signal name="changed">
+ <description>
+ </description>
+ </signal>
+ </signals>
+ <constants>
+ </constants>
+</class>
diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml
index 205b342ba8..fd52a28486 100644
--- a/doc/classes/TileMap.xml
+++ b/doc/classes/TileMap.xml
@@ -31,53 +31,46 @@
Clears cells that do not exist in the tileset.
</description>
</method>
- <method name="get_cell" qualifiers="const">
+ <method name="get_cell_alternative_tile" qualifiers="const">
<return type="int">
</return>
- <argument index="0" name="x" type="int">
- </argument>
- <argument index="1" name="y" type="int">
+ <argument index="0" name="coords" type="Vector2i">
</argument>
<description>
- Returns the tile index of the given cell. If no tile exists in the cell, returns [constant INVALID_CELL].
</description>
</method>
- <method name="get_cell_autotile_coord" qualifiers="const">
- <return type="Vector2">
+ <method name="get_cell_atlas_coords" qualifiers="const">
+ <return type="Vector2i">
</return>
- <argument index="0" name="x" type="int">
- </argument>
- <argument index="1" name="y" type="int">
+ <argument index="0" name="coords" type="Vector2i">
</argument>
<description>
- Returns the coordinate (subtile column and row) of the autotile variation in the tileset. Returns a zero vector if the cell doesn't have autotiling.
</description>
</method>
- <method name="get_cellv" qualifiers="const">
+ <method name="get_cell_source_id" qualifiers="const">
<return type="int">
</return>
- <argument index="0" name="position" type="Vector2">
+ <argument index="0" name="coords" type="Vector2i">
</argument>
<description>
- Returns the tile index of the cell given by a Vector2. If no tile exists in the cell, returns [constant INVALID_CELL].
</description>
</method>
- <method name="get_collision_layer_bit" qualifiers="const">
- <return type="bool">
+ <method name="get_neighbor_cell" qualifiers="const">
+ <return type="Vector2i">
</return>
- <argument index="0" name="bit" type="int">
+ <argument index="0" name="coords" type="Vector2i">
+ </argument>
+ <argument index="1" name="neighbor" type="int" enum="TileSet.CellNeighbor">
</argument>
<description>
- Returns [code]true[/code] if the given collision layer bit is set.
</description>
</method>
- <method name="get_collision_mask_bit" qualifiers="const">
- <return type="bool">
+ <method name="get_surrounding_tiles">
+ <return type="Vector2i[]">
</return>
- <argument index="0" name="bit" type="int">
+ <argument index="0" name="coords" type="Vector2i">
</argument>
<description>
- Returns [code]true[/code] if the given collision mask bit is set.
</description>
</method>
<method name="get_used_cells" qualifiers="const">
@@ -87,15 +80,6 @@
Returns a [Vector2] array with the positions of all cells containing a tile from the tileset (i.e. a tile index different from [code]-1[/code]).
</description>
</method>
- <method name="get_used_cells_by_index" qualifiers="const">
- <return type="Vector2i[]">
- </return>
- <argument index="0" name="index" type="int">
- </argument>
- <description>
- Returns an array of all cells with the given tile [code]index[/code].
- </description>
- </method>
<method name="get_used_rect">
<return type="Rect2">
</return>
@@ -103,155 +87,28 @@
Returns a rectangle enclosing the used (non-empty) tiles of the map.
</description>
</method>
- <method name="is_cell_transposed" qualifiers="const">
- <return type="bool">
- </return>
- <argument index="0" name="x" type="int">
- </argument>
- <argument index="1" name="y" type="int">
- </argument>
- <description>
- Returns [code]true[/code] if the given cell is transposed, i.e. the X and Y axes are swapped.
- </description>
- </method>
- <method name="is_cell_x_flipped" qualifiers="const">
- <return type="bool">
- </return>
- <argument index="0" name="x" type="int">
- </argument>
- <argument index="1" name="y" type="int">
- </argument>
- <description>
- Returns [code]true[/code] if the given cell is flipped in the X axis.
- </description>
- </method>
- <method name="is_cell_y_flipped" qualifiers="const">
- <return type="bool">
- </return>
- <argument index="0" name="x" type="int">
- </argument>
- <argument index="1" name="y" type="int">
- </argument>
- <description>
- Returns [code]true[/code] if the given cell is flipped in the Y axis.
- </description>
- </method>
<method name="map_to_world" qualifiers="const">
<return type="Vector2">
</return>
- <argument index="0" name="map_position" type="Vector2">
- </argument>
- <argument index="1" name="ignore_half_ofs" type="bool" default="false">
+ <argument index="0" name="map_position" type="Vector2i">
</argument>
<description>
Returns the local position corresponding to the given tilemap (grid-based) coordinates.
- Optionally, the tilemap's half offset can be ignored.
</description>
</method>
<method name="set_cell">
<return type="void">
</return>
- <argument index="0" name="x" type="int">
- </argument>
- <argument index="1" name="y" type="int">
- </argument>
- <argument index="2" name="tile" type="int">
- </argument>
- <argument index="3" name="flip_x" type="bool" default="false">
- </argument>
- <argument index="4" name="flip_y" type="bool" default="false">
- </argument>
- <argument index="5" name="transpose" type="bool" default="false">
- </argument>
- <argument index="6" name="autotile_coord" type="Vector2" default="Vector2( 0, 0 )">
- </argument>
- <description>
- Sets the tile index for the cell given by a Vector2.
- An index of [code]-1[/code] clears the cell.
- Optionally, the tile can also be flipped, transposed, or given autotile coordinates. The autotile coordinate refers to the column and row of the subtile.
- [b]Note:[/b] Data such as navigation polygons and collision shapes are not immediately updated for performance reasons.
- If you need these to be immediately updated, you can call [method update_dirty_quadrants].
- Overriding this method also overrides it internally, allowing custom logic to be implemented when tiles are placed/removed:
- [codeblocks]
- [gdscript]
- func set_cell(x, y, tile, flip_x=false, flip_y=false, transpose=false, autotile_coord=Vector2()):
- # Write your custom logic here.
- # To call the default method:
- .set_cell(x, y, tile, flip_x, flip_y, transpose, autotile_coord)
- [/gdscript]
- [csharp]
- public void SetCell(int x, int y, int tile, bool flipX = false, bool flipY = false, bool transpose = false, Vector2 autotileCoord = new Vector2())
- {
- // Write your custom logic here.
- // To call the default method:
- base.SetCell(x, y, tile, flipX, flipY, transpose, autotileCoord);
- }
- [/csharp]
- [/codeblocks]
- </description>
- </method>
- <method name="set_cellv">
- <return type="void">
- </return>
- <argument index="0" name="position" type="Vector2">
- </argument>
- <argument index="1" name="tile" type="int">
- </argument>
- <argument index="2" name="flip_x" type="bool" default="false">
- </argument>
- <argument index="3" name="flip_y" type="bool" default="false">
- </argument>
- <argument index="4" name="transpose" type="bool" default="false">
- </argument>
- <description>
- Sets the tile index for the given cell.
- An index of [code]-1[/code] clears the cell.
- Optionally, the tile can also be flipped or transposed.
- [b]Note:[/b] Data such as navigation polygons and collision shapes are not immediately updated for performance reasons.
- If you need these to be immediately updated, you can call [method update_dirty_quadrants].
- </description>
- </method>
- <method name="set_collision_layer_bit">
- <return type="void">
- </return>
- <argument index="0" name="bit" type="int">
- </argument>
- <argument index="1" name="value" type="bool">
- </argument>
- <description>
- Sets the given collision layer bit.
- </description>
- </method>
- <method name="set_collision_mask_bit">
- <return type="void">
- </return>
- <argument index="0" name="bit" type="int">
+ <argument index="0" name="coords" type="Vector2i">
</argument>
- <argument index="1" name="value" type="bool">
+ <argument index="1" name="source_id" type="int" default="-1">
</argument>
- <description>
- Sets the given collision mask bit.
- </description>
- </method>
- <method name="update_bitmask_area">
- <return type="void">
- </return>
- <argument index="0" name="position" type="Vector2">
- </argument>
- <description>
- Applies autotiling rules to the cell (and its adjacent cells) referenced by its grid-based X and Y coordinates.
- </description>
- </method>
- <method name="update_bitmask_region">
- <return type="void">
- </return>
- <argument index="0" name="start" type="Vector2" default="Vector2( 0, 0 )">
+ <argument index="2" name="atlas_coords" type="Vector2i" default="Vector2i( -1, -1 )">
</argument>
- <argument index="1" name="end" type="Vector2" default="Vector2( 0, 0 )">
+ <argument index="3" name="alternative_tile" type="int" default="-1">
</argument>
<description>
- Applies autotiling rules to the cells in the given region (specified by grid-based X and Y coordinates).
- Calling with invalid (or missing) parameters applies autotiling rules for the entire tilemap.
+ Sets the tile index for the cell given by a Vector2i.
</description>
</method>
<method name="update_dirty_quadrants">
@@ -262,7 +119,7 @@
</description>
</method>
<method name="world_to_map" qualifiers="const">
- <return type="Vector2">
+ <return type="Vector2i">
</return>
<argument index="0" name="world_position" type="Vector2">
</argument>
@@ -272,110 +129,20 @@
</method>
</methods>
<members>
- <member name="bake_navigation" type="bool" setter="set_bake_navigation" getter="is_baking_navigation" default="false">
- If [code]true[/code], this TileMap bakes a navigation region.
- </member>
- <member name="cell_clip_uv" type="bool" setter="set_clip_uv" getter="get_clip_uv" default="false">
- If [code]true[/code], the cell's UVs will be clipped.
- </member>
- <member name="cell_custom_transform" type="Transform2D" setter="set_custom_transform" getter="get_custom_transform" default="Transform2D( 64, 0, 0, 64, 0, 0 )">
- The custom [Transform2D] to be applied to the TileMap's cells.
- </member>
- <member name="cell_half_offset" type="int" setter="set_half_offset" getter="get_half_offset" enum="TileMap.HalfOffset" default="2">
- Amount to offset alternating tiles. See [enum HalfOffset] for possible values.
- </member>
<member name="cell_quadrant_size" type="int" setter="set_quadrant_size" getter="get_quadrant_size" default="16">
The TileMap's quadrant size. Optimizes drawing by batching, using chunks of this size.
</member>
- <member name="cell_size" type="Vector2" setter="set_cell_size" getter="get_cell_size" default="Vector2( 64, 64 )">
- The TileMap's cell size.
- </member>
- <member name="cell_tile_origin" type="int" setter="set_tile_origin" getter="get_tile_origin" enum="TileMap.TileOrigin" default="0">
- Position for tile origin. See [enum TileOrigin] for possible values.
- </member>
- <member name="cell_y_sort" type="bool" setter="set_y_sort_enabled" getter="is_y_sort_enabled" default="false">
- If [code]true[/code], the TileMap's direct children will be drawn in order of their Y coordinate.
- </member>
- <member name="centered_textures" type="bool" setter="set_centered_textures" getter="is_centered_textures_enabled" default="false">
- If [code]true[/code], the textures will be centered in the middle of each tile. This is useful for certain isometric or top-down modes when textures are made larger or smaller than the tiles (e.g. to avoid flickering on tile edges). The offset is still applied, but from the center of the tile. If used, [member compatibility_mode] is ignored.
- If [code]false[/code], the texture position start in the top-left corner unless [member compatibility_mode] is enabled.
- </member>
- <member name="collision_bounce" type="float" setter="set_collision_bounce" getter="get_collision_bounce" default="0.0">
- Bounce value for static body collisions (see [code]collision_use_kinematic[/code]).
- </member>
- <member name="collision_friction" type="float" setter="set_collision_friction" getter="get_collision_friction" default="1.0">
- Friction value for static body collisions (see [code]collision_use_kinematic[/code]).
- </member>
- <member name="collision_layer" type="int" setter="set_collision_layer" getter="get_collision_layer" default="1">
- The collision layer(s) for all colliders in the TileMap. See [url=https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information.
- </member>
- <member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="1">
- The collision mask(s) for all colliders in the TileMap. See [url=https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information.
- </member>
- <member name="collision_use_kinematic" type="bool" setter="set_collision_use_kinematic" getter="get_collision_use_kinematic" default="false">
- If [code]true[/code], TileMap collisions will be handled as a kinematic body. If [code]false[/code], collisions will be handled as static body.
- </member>
- <member name="collision_use_parent" type="bool" setter="set_collision_use_parent" getter="get_collision_use_parent" default="false">
- If [code]true[/code], this tilemap's collision shape will be added to the collision shape of the parent. The parent has to be a [CollisionObject2D].
- </member>
- <member name="compatibility_mode" type="bool" setter="set_compatibility_mode" getter="is_compatibility_mode_enabled" default="false">
- If [code]true[/code], the compatibility with the tilemaps made in Godot 3.1 or earlier is maintained (textures move when the tile origin changes and rotate if the texture size is not homogeneous). This mode presents problems when doing [code]flip_h[/code], [code]flip_v[/code] and [code]transpose[/code] tile operations on non-homogeneous isometric tiles (e.g. 2:1), in which the texture could not coincide with the collision, thus it is not recommended for isometric or non-square tiles.
- If [code]false[/code], the textures do not move when doing [code]flip_h[/code], [code]flip_v[/code] operations if no offset is used, nor when changing the tile origin.
- The compatibility mode doesn't work with the [member centered_textures] option, because displacing textures with the [member cell_tile_origin] option or in irregular tiles is not relevant when centering those textures.
- </member>
- <member name="mode" type="int" setter="set_mode" getter="get_mode" enum="TileMap.Mode" default="0">
- The TileMap orientation mode. See [enum Mode] for possible values.
- </member>
- <member name="occluder_light_mask" type="int" setter="set_occluder_light_mask" getter="get_occluder_light_mask" default="1">
- The light mask assigned to all light occluders in the TileMap. The TileSet's light occluders will cast shadows only from Light2D(s) that have the same light mask(s).
- </member>
<member name="tile_set" type="TileSet" setter="set_tileset" getter="get_tileset">
The assigned [TileSet].
</member>
</members>
<signals>
- <signal name="settings_changed">
+ <signal name="changed">
<description>
- Emitted when a tilemap setting has changed.
+ Emitted when the [TileSet] of this TileMap changes.
</description>
</signal>
</signals>
<constants>
- <constant name="INVALID_CELL" value="-1">
- Returned when a cell doesn't exist.
- </constant>
- <constant name="MODE_SQUARE" value="0" enum="Mode">
- Orthogonal orientation mode.
- </constant>
- <constant name="MODE_ISOMETRIC" value="1" enum="Mode">
- Isometric orientation mode.
- </constant>
- <constant name="MODE_CUSTOM" value="2" enum="Mode">
- Custom orientation mode.
- </constant>
- <constant name="HALF_OFFSET_X" value="0" enum="HalfOffset">
- Half offset on the X coordinate.
- </constant>
- <constant name="HALF_OFFSET_Y" value="1" enum="HalfOffset">
- Half offset on the Y coordinate.
- </constant>
- <constant name="HALF_OFFSET_DISABLED" value="2" enum="HalfOffset">
- Half offset disabled.
- </constant>
- <constant name="HALF_OFFSET_NEGATIVE_X" value="3" enum="HalfOffset">
- Half offset on the X coordinate (negative).
- </constant>
- <constant name="HALF_OFFSET_NEGATIVE_Y" value="4" enum="HalfOffset">
- Half offset on the Y coordinate (negative).
- </constant>
- <constant name="TILE_ORIGIN_TOP_LEFT" value="0" enum="TileOrigin">
- Tile origin at its top-left corner.
- </constant>
- <constant name="TILE_ORIGIN_CENTER" value="1" enum="TileOrigin">
- Tile origin at its center.
- </constant>
- <constant name="TILE_ORIGIN_BOTTOM_LEFT" value="2" enum="TileOrigin">
- Tile origin at its bottom-left corner.
- </constant>
</constants>
</class>
diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml
index adc5880c71..ff68a017a1 100644
--- a/doc/classes/TileSet.xml
+++ b/doc/classes/TileSet.xml
@@ -17,752 +17,350 @@
<link title="2D Kinematic Character Demo">https://godotengine.org/asset-library/asset/113</link>
</tutorials>
<methods>
- <method name="_forward_atlas_subtile_selection" qualifiers="virtual">
- <return type="Vector2">
- </return>
- <argument index="0" name="atlastile_id" type="int">
- </argument>
- <argument index="1" name="tilemap" type="Object">
- </argument>
- <argument index="2" name="tile_location" type="Vector2">
- </argument>
- <description>
- </description>
- </method>
- <method name="_forward_subtile_selection" qualifiers="virtual">
- <return type="Vector2">
- </return>
- <argument index="0" name="autotile_id" type="int">
- </argument>
- <argument index="1" name="bitmask" type="int">
- </argument>
- <argument index="2" name="tilemap" type="Object">
- </argument>
- <argument index="3" name="tile_location" type="Vector2">
- </argument>
- <description>
- </description>
- </method>
- <method name="_is_tile_bound" qualifiers="virtual">
- <return type="bool">
- </return>
- <argument index="0" name="drawn_id" type="int">
- </argument>
- <argument index="1" name="neighbor_id" type="int">
- </argument>
- <description>
- Determines when the auto-tiler should consider two different auto-tile IDs to be bound together.
- [b]Note:[/b] [code]neighbor_id[/code] will be [code]-1[/code] ([constant TileMap.INVALID_CELL]) when checking a tile against an empty neighbor tile.
- </description>
- </method>
- <method name="autotile_clear_bitmask_map">
- <return type="void">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <description>
- Clears all bitmask information of the autotile.
- </description>
- </method>
- <method name="autotile_get_bitmask">
+ <method name="add_source">
<return type="int">
</return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="coord" type="Vector2">
+ <argument index="0" name="atlas_source_id_override" type="TileSetAtlasSource">
</argument>
- <description>
- Returns the bitmask of the subtile from an autotile given its coordinates.
- The value is the sum of the values in [enum AutotileBindings] present in the subtile (e.g. a value of 5 means the bitmask has bindings in both the top left and top right).
- </description>
- </method>
- <method name="autotile_get_bitmask_mode" qualifiers="const">
- <return type="int" enum="TileSet.BitmaskMode">
- </return>
- <argument index="0" name="id" type="int">
+ <argument index="1" name="arg1" type="int" default="-1">
</argument>
<description>
- Returns the [enum BitmaskMode] of the autotile.
</description>
</method>
- <method name="autotile_get_icon_coordinate" qualifiers="const">
- <return type="Vector2">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <description>
- Returns the subtile that's being used as an icon in an atlas/autotile given its coordinates.
- The subtile defined as the icon will be used as a fallback when the atlas/autotile's bitmask information is incomplete. It will also be used to represent it in the TileSet editor.
- </description>
- </method>
- <method name="autotile_get_light_occluder" qualifiers="const">
- <return type="OccluderPolygon2D">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="coord" type="Vector2">
- </argument>
- <description>
- Returns the light occluder of the subtile from an atlas/autotile given its coordinates.
- </description>
- </method>
- <method name="autotile_get_navigation_polygon" qualifiers="const">
- <return type="NavigationPolygon">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="coord" type="Vector2">
- </argument>
- <description>
- Returns the navigation polygon of the subtile from an atlas/autotile given its coordinates.
- </description>
- </method>
- <method name="autotile_get_size" qualifiers="const">
- <return type="Vector2">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <description>
- Returns the size of the subtiles in an atlas/autotile.
- </description>
- </method>
- <method name="autotile_get_spacing" qualifiers="const">
+ <method name="get_navigation_layer_layers" qualifiers="const">
<return type="int">
</return>
- <argument index="0" name="id" type="int">
+ <argument index="0" name="layer_index" type="int">
</argument>
<description>
- Returns the spacing between subtiles of the atlas/autotile.
</description>
</method>
- <method name="autotile_get_subtile_priority">
+ <method name="get_next_source_id" qualifiers="const">
<return type="int">
</return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="coord" type="Vector2">
- </argument>
<description>
- Returns the priority of the subtile from an autotile given its coordinates.
- When more than one subtile has the same bitmask value, one of them will be picked randomly for drawing. Its priority will define how often it will be picked.
</description>
</method>
- <method name="autotile_get_z_index">
+ <method name="get_occlusion_layer_light_mask" qualifiers="const">
<return type="int">
</return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="coord" type="Vector2">
+ <argument index="0" name="arg0" type="int">
</argument>
<description>
- Returns the drawing index of the subtile from an atlas/autotile given its coordinates.
</description>
</method>
- <method name="autotile_set_bitmask">
- <return type="void">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="bitmask" type="Vector2">
- </argument>
- <argument index="2" name="flag" type="int">
- </argument>
- <description>
- Sets the bitmask of the subtile from an autotile given its coordinates.
- The value is the sum of the values in [enum AutotileBindings] present in the subtile (e.g. a value of 5 means the bitmask has bindings in both the top left and top right).
- </description>
- </method>
- <method name="autotile_set_bitmask_mode">
- <return type="void">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="mode" type="int" enum="TileSet.BitmaskMode">
- </argument>
- <description>
- Sets the [enum BitmaskMode] of the autotile.
- </description>
- </method>
- <method name="autotile_set_icon_coordinate">
- <return type="void">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="coord" type="Vector2">
- </argument>
- <description>
- Sets the subtile that will be used as an icon in an atlas/autotile given its coordinates.
- The subtile defined as the icon will be used as a fallback when the atlas/autotile's bitmask information is incomplete. It will also be used to represent it in the TileSet editor.
- </description>
- </method>
- <method name="autotile_set_light_occluder">
- <return type="void">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="light_occluder" type="OccluderPolygon2D">
- </argument>
- <argument index="2" name="coord" type="Vector2">
- </argument>
- <description>
- Sets the light occluder of the subtile from an atlas/autotile given its coordinates.
- </description>
- </method>
- <method name="autotile_set_navigation_polygon">
- <return type="void">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="navigation_polygon" type="NavigationPolygon">
- </argument>
- <argument index="2" name="coord" type="Vector2">
- </argument>
- <description>
- Sets the navigation polygon of the subtile from an atlas/autotile given its coordinates.
- </description>
- </method>
- <method name="autotile_set_size">
- <return type="void">
+ <method name="get_occlusion_layer_sdf_collision" qualifiers="const">
+ <return type="bool">
</return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="size" type="Vector2">
+ <argument index="0" name="arg0" type="int">
</argument>
<description>
- Sets the size of the subtiles in an atlas/autotile.
</description>
</method>
- <method name="autotile_set_spacing">
- <return type="void">
+ <method name="get_physics_layer_collision_layer" qualifiers="const">
+ <return type="int">
</return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="spacing" type="int">
+ <argument index="0" name="layer_index" type="int">
</argument>
<description>
- Sets the spacing between subtiles of the atlas/autotile.
</description>
</method>
- <method name="autotile_set_subtile_priority">
- <return type="void">
+ <method name="get_physics_layer_collision_mask" qualifiers="const">
+ <return type="int">
</return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="coord" type="Vector2">
- </argument>
- <argument index="2" name="priority" type="int">
+ <argument index="0" name="layer_index" type="int">
</argument>
<description>
- Sets the priority of the subtile from an autotile given its coordinates.
- When more than one subtile has the same bitmask value, one of them will be picked randomly for drawing. Its priority will define how often it will be picked.
</description>
</method>
- <method name="autotile_set_z_index">
- <return type="void">
+ <method name="get_physics_layer_physics_material" qualifiers="const">
+ <return type="PhysicsMaterial">
</return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="coord" type="Vector2">
- </argument>
- <argument index="2" name="z_index" type="int">
+ <argument index="0" name="layer_index" type="int">
</argument>
<description>
- Sets the drawing index of the subtile from an atlas/autotile given its coordinates.
</description>
</method>
- <method name="clear">
- <return type="void">
+ <method name="get_source" qualifiers="const">
+ <return type="TileSetSource">
</return>
- <description>
- Clears all tiles.
- </description>
- </method>
- <method name="create_tile">
- <return type="void">
- </return>
- <argument index="0" name="id" type="int">
+ <argument index="0" name="index" type="int">
</argument>
<description>
- Creates a new tile with the given ID.
</description>
</method>
- <method name="find_tile_by_name" qualifiers="const">
+ <method name="get_source_count" qualifiers="const">
<return type="int">
</return>
- <argument index="0" name="name" type="String">
- </argument>
<description>
- Returns the first tile matching the given name.
</description>
</method>
- <method name="get_last_unused_tile_id" qualifiers="const">
+ <method name="get_source_id" qualifiers="const">
<return type="int">
</return>
- <description>
- Returns the ID following the last currently used ID, useful when creating a new tile.
- </description>
- </method>
- <method name="get_tiles_ids" qualifiers="const">
- <return type="Array">
- </return>
- <description>
- Returns an array of all currently used tile IDs.
- </description>
- </method>
- <method name="remove_tile">
- <return type="void">
- </return>
- <argument index="0" name="id" type="int">
+ <argument index="0" name="index" type="int">
</argument>
<description>
- Removes the given tile ID.
</description>
</method>
- <method name="tile_add_shape">
- <return type="void">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="shape" type="Shape2D">
- </argument>
- <argument index="2" name="shape_transform" type="Transform2D">
- </argument>
- <argument index="3" name="one_way" type="bool" default="false">
- </argument>
- <argument index="4" name="autotile_coord" type="Vector2" default="Vector2( 0, 0 )">
- </argument>
- <description>
- Adds a shape to the tile.
- </description>
- </method>
- <method name="tile_get_light_occluder" qualifiers="const">
- <return type="OccluderPolygon2D">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <description>
- Returns the tile's light occluder.
- </description>
- </method>
- <method name="tile_get_material" qualifiers="const">
- <return type="ShaderMaterial">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <description>
- Returns the tile's material.
- </description>
- </method>
- <method name="tile_get_modulate" qualifiers="const">
+ <method name="get_terrain_color" qualifiers="const">
<return type="Color">
</return>
- <argument index="0" name="id" type="int">
- </argument>
- <description>
- Returns the tile's modulation color.
- </description>
- </method>
- <method name="tile_get_name" qualifiers="const">
- <return type="String">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <description>
- Returns the tile's name.
- </description>
- </method>
- <method name="tile_get_navigation_polygon" qualifiers="const">
- <return type="NavigationPolygon">
- </return>
- <argument index="0" name="id" type="int">
+ <argument index="0" name="terrain_set" type="int">
</argument>
- <description>
- Returns the navigation polygon of the tile.
- </description>
- </method>
- <method name="tile_get_navigation_polygon_offset" qualifiers="const">
- <return type="Vector2">
- </return>
- <argument index="0" name="id" type="int">
+ <argument index="1" name="terrain_index" type="int">
</argument>
<description>
- Returns the offset of the tile's navigation polygon.
</description>
</method>
- <method name="tile_get_occluder_offset" qualifiers="const">
- <return type="Vector2">
+ <method name="get_terrain_name" qualifiers="const">
+ <return type="String">
</return>
- <argument index="0" name="id" type="int">
+ <argument index="0" name="terrain_set" type="int">
</argument>
- <description>
- Returns the offset of the tile's light occluder.
- </description>
- </method>
- <method name="tile_get_region" qualifiers="const">
- <return type="Rect2">
- </return>
- <argument index="0" name="id" type="int">
+ <argument index="1" name="terrain_index" type="int">
</argument>
<description>
- Returns the tile sub-region in the texture.
</description>
</method>
- <method name="tile_get_shape" qualifiers="const">
- <return type="Shape2D">
+ <method name="get_terrain_set_mode" qualifiers="const">
+ <return type="int" enum="TileSet.TerrainMode">
</return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="shape_id" type="int">
+ <argument index="0" name="terrain_set" type="int">
</argument>
<description>
- Returns a tile's given shape.
</description>
</method>
- <method name="tile_get_shape_count" qualifiers="const">
+ <method name="get_terrains_count" qualifiers="const">
<return type="int">
</return>
- <argument index="0" name="id" type="int">
- </argument>
- <description>
- Returns the number of shapes assigned to a tile.
- </description>
- </method>
- <method name="tile_get_shape_offset" qualifiers="const">
- <return type="Vector2">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="shape_id" type="int">
+ <argument index="0" name="terrain_set" type="int">
</argument>
<description>
- Returns the offset of a tile's shape.
</description>
</method>
- <method name="tile_get_shape_one_way" qualifiers="const">
+ <method name="has_source" qualifiers="const">
<return type="bool">
</return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="shape_id" type="int">
- </argument>
- <description>
- Returns the one-way collision value of a tile's shape.
- </description>
- </method>
- <method name="tile_get_shape_one_way_margin" qualifiers="const">
- <return type="float">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="shape_id" type="int">
- </argument>
- <description>
- </description>
- </method>
- <method name="tile_get_shape_transform" qualifiers="const">
- <return type="Transform2D">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="shape_id" type="int">
- </argument>
- <description>
- Returns the [Transform2D] of a tile's shape.
- </description>
- </method>
- <method name="tile_get_shapes" qualifiers="const">
- <return type="Array">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <description>
- Returns an array of dictionaries describing the tile's shapes.
- [b]Dictionary structure in the array returned by this method:[/b]
- [codeblock]
- {
- "autotile_coord": Vector2,
- "one_way": bool,
- "one_way_margin": int,
- "shape": CollisionShape2D,
- "shape_transform": Transform2D,
- }
- [/codeblock]
- </description>
- </method>
- <method name="tile_get_texture" qualifiers="const">
- <return type="Texture2D">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <description>
- Returns the tile's texture.
- </description>
- </method>
- <method name="tile_get_texture_offset" qualifiers="const">
- <return type="Vector2">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <description>
- Returns the texture offset of the tile.
- </description>
- </method>
- <method name="tile_get_tile_mode" qualifiers="const">
- <return type="int" enum="TileSet.TileMode">
- </return>
- <argument index="0" name="id" type="int">
+ <argument index="0" name="index" type="int">
</argument>
<description>
- Returns the tile's [enum TileMode].
</description>
</method>
- <method name="tile_get_z_index" qualifiers="const">
- <return type="int">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <description>
- Returns the tile's Z index (drawing layer).
- </description>
- </method>
- <method name="tile_set_light_occluder">
- <return type="void">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="light_occluder" type="OccluderPolygon2D">
- </argument>
- <description>
- Sets a light occluder for the tile.
- </description>
- </method>
- <method name="tile_set_material">
- <return type="void">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="material" type="ShaderMaterial">
- </argument>
- <description>
- Sets the tile's material.
- </description>
- </method>
- <method name="tile_set_modulate">
- <return type="void">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="color" type="Color">
- </argument>
- <description>
- Sets the tile's modulation color.
- </description>
- </method>
- <method name="tile_set_name">
- <return type="void">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="name" type="String">
- </argument>
- <description>
- Sets the tile's name.
- </description>
- </method>
- <method name="tile_set_navigation_polygon">
+ <method name="remove_source">
<return type="void">
</return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="navigation_polygon" type="NavigationPolygon">
+ <argument index="0" name="source_id" type="int">
</argument>
<description>
- Sets the tile's navigation polygon.
</description>
</method>
- <method name="tile_set_navigation_polygon_offset">
+ <method name="set_navigation_layer_layers">
<return type="void">
</return>
- <argument index="0" name="id" type="int">
+ <argument index="0" name="layer_index" type="int">
</argument>
- <argument index="1" name="navigation_polygon_offset" type="Vector2">
+ <argument index="1" name="layers" type="int">
</argument>
<description>
- Sets an offset for the tile's navigation polygon.
</description>
</method>
- <method name="tile_set_occluder_offset">
+ <method name="set_occlusion_layer_light_mask">
<return type="void">
</return>
- <argument index="0" name="id" type="int">
+ <argument index="0" name="layer_index" type="int">
</argument>
- <argument index="1" name="occluder_offset" type="Vector2">
+ <argument index="1" name="light_mask" type="int">
</argument>
<description>
- Sets an offset for the tile's light occluder.
</description>
</method>
- <method name="tile_set_region">
+ <method name="set_occlusion_layer_sdf_collision">
<return type="void">
</return>
- <argument index="0" name="id" type="int">
+ <argument index="0" name="layer_index" type="int">
</argument>
- <argument index="1" name="region" type="Rect2">
+ <argument index="1" name="sdf_collision" type="int">
</argument>
<description>
- Sets the tile's sub-region in the texture. This is common in texture atlases.
</description>
</method>
- <method name="tile_set_shape">
+ <method name="set_physics_layer_collision_layer">
<return type="void">
</return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="shape_id" type="int">
+ <argument index="0" name="layer_index" type="int">
</argument>
- <argument index="2" name="shape" type="Shape2D">
+ <argument index="1" name="layer" type="int">
</argument>
<description>
- Sets a shape for the tile, enabling collision.
</description>
</method>
- <method name="tile_set_shape_offset">
+ <method name="set_physics_layer_collision_mask">
<return type="void">
</return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="shape_id" type="int">
+ <argument index="0" name="layer_index" type="int">
</argument>
- <argument index="2" name="shape_offset" type="Vector2">
+ <argument index="1" name="mask" type="int">
</argument>
<description>
- Sets the offset of a tile's shape.
</description>
</method>
- <method name="tile_set_shape_one_way">
+ <method name="set_physics_layer_physics_material">
<return type="void">
</return>
- <argument index="0" name="id" type="int">
+ <argument index="0" name="layer_index" type="int">
</argument>
- <argument index="1" name="shape_id" type="int">
- </argument>
- <argument index="2" name="one_way" type="bool">
+ <argument index="1" name="physics_material" type="PhysicsMaterial">
</argument>
<description>
- Enables one-way collision on a tile's shape.
</description>
</method>
- <method name="tile_set_shape_one_way_margin">
+ <method name="set_source_id">
<return type="void">
</return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="shape_id" type="int">
+ <argument index="0" name="source_id" type="int">
</argument>
- <argument index="2" name="one_way" type="float">
+ <argument index="1" name="arg1" type="int">
</argument>
<description>
</description>
</method>
- <method name="tile_set_shape_transform">
+ <method name="set_terrain_color">
<return type="void">
</return>
- <argument index="0" name="id" type="int">
+ <argument index="0" name="terrain_set" type="int">
</argument>
- <argument index="1" name="shape_id" type="int">
+ <argument index="1" name="terrain_index" type="int">
</argument>
- <argument index="2" name="shape_transform" type="Transform2D">
+ <argument index="2" name="color" type="Color">
</argument>
<description>
- Sets a [Transform2D] on a tile's shape.
</description>
</method>
- <method name="tile_set_shapes">
+ <method name="set_terrain_name">
<return type="void">
</return>
- <argument index="0" name="id" type="int">
+ <argument index="0" name="terrain_set" type="int">
</argument>
- <argument index="1" name="shapes" type="Array">
- </argument>
- <description>
- Sets an array of shapes for the tile, enabling collision.
- </description>
- </method>
- <method name="tile_set_texture">
- <return type="void">
- </return>
- <argument index="0" name="id" type="int">
- </argument>
- <argument index="1" name="texture" type="Texture2D">
- </argument>
- <description>
- Sets the tile's texture.
- </description>
- </method>
- <method name="tile_set_texture_offset">
- <return type="void">
- </return>
- <argument index="0" name="id" type="int">
+ <argument index="1" name="terrain_index" type="int">
</argument>
- <argument index="1" name="texture_offset" type="Vector2">
+ <argument index="2" name="name" type="String">
</argument>
<description>
- Sets the tile's texture offset.
</description>
</method>
- <method name="tile_set_tile_mode">
+ <method name="set_terrain_set_mode">
<return type="void">
</return>
- <argument index="0" name="id" type="int">
+ <argument index="0" name="terrain_set" type="int">
</argument>
- <argument index="1" name="tilemode" type="int" enum="TileSet.TileMode">
+ <argument index="1" name="mode" type="int" enum="TileSet.TerrainMode">
</argument>
<description>
- Sets the tile's [enum TileMode].
</description>
</method>
- <method name="tile_set_z_index">
+ <method name="set_terrains_count">
<return type="void">
</return>
- <argument index="0" name="id" type="int">
+ <argument index="0" name="terrain_set" type="int">
</argument>
- <argument index="1" name="z_index" type="int">
+ <argument index="1" name="terrains_count" type="int">
</argument>
<description>
- Sets the tile's drawing index.
</description>
</method>
</methods>
+ <members>
+ <member name="custom_data_layers_count" type="int" setter="set_custom_data_layers_count" getter="get_custom_data_layers_count" default="0">
+ </member>
+ <member name="navigation_layers_count" type="int" setter="set_navigation_layers_count" getter="get_navigation_layers_count" default="0">
+ </member>
+ <member name="occlusion_layers_count" type="int" setter="set_occlusion_layers_count" getter="get_occlusion_layers_count" default="0">
+ </member>
+ <member name="physics_layers_count" type="int" setter="set_physics_layers_count" getter="get_physics_layers_count" default="0">
+ </member>
+ <member name="terrains_sets_count" type="int" setter="set_terrain_sets_count" getter="get_terrain_sets_count" default="0">
+ </member>
+ <member name="tile_layout" type="int" setter="set_tile_layout" getter="get_tile_layout" enum="TileSet.TileLayout" default="0">
+ </member>
+ <member name="tile_offset_axis" type="int" setter="set_tile_offset_axis" getter="get_tile_offset_axis" enum="TileSet.TileOffsetAxis" default="0">
+ </member>
+ <member name="tile_shape" type="int" setter="set_tile_shape" getter="get_tile_shape" enum="TileSet.TileShape" default="0">
+ </member>
+ <member name="tile_size" type="Vector2i" setter="set_tile_size" getter="get_tile_size" default="Vector2i( 16, 16 )">
+ </member>
+ <member name="tile_skew" type="Vector2" setter="set_tile_skew" getter="get_tile_skew" default="Vector2( 0, 0 )">
+ </member>
+ <member name="uv_clipping" type="bool" setter="set_uv_clipping" getter="is_uv_clipping" default="false">
+ </member>
+ <member name="y_sorting" type="bool" setter="set_y_sorting" getter="is_y_sorting" default="false">
+ </member>
+ </members>
<constants>
- <constant name="BITMASK_2X2" value="0" enum="BitmaskMode">
+ <constant name="TILE_SHAPE_SQUARE" value="0" enum="TileShape">
+ Orthogonal orientation mode.
+ </constant>
+ <constant name="TILE_SHAPE_ISOMETRIC" value="1" enum="TileShape">
+ Isometric orientation mode.
+ </constant>
+ <constant name="TILE_SHAPE_HALF_OFFSET_SQUARE" value="2" enum="TileShape">
+ </constant>
+ <constant name="TILE_SHAPE_HEXAGON" value="3" enum="TileShape">
+ Hexagon orientation mode.
+ </constant>
+ <constant name="TILE_LAYOUT_STACKED" value="0" enum="TileLayout">
+ </constant>
+ <constant name="TILE_LAYOUT_STACKED_OFFSET" value="1" enum="TileLayout">
+ </constant>
+ <constant name="TILE_LAYOUT_STAIRS_RIGHT" value="2" enum="TileLayout">
+ </constant>
+ <constant name="TILE_LAYOUT_STAIRS_DOWN" value="3" enum="TileLayout">
+ </constant>
+ <constant name="TILE_LAYOUT_DIAMOND_RIGHT" value="4" enum="TileLayout">
+ </constant>
+ <constant name="TILE_LAYOUT_DIAMOND_DOWN" value="5" enum="TileLayout">
+ </constant>
+ <constant name="TILE_OFFSET_AXIS_HORIZONTAL" value="0" enum="TileOffsetAxis">
+ </constant>
+ <constant name="TILE_OFFSET_AXIS_VERTICAL" value="1" enum="TileOffsetAxis">
+ </constant>
+ <constant name="TileSet::CELL_NEIGHBOR_RIGHT_SIDE" value="0" enum="CellNeighbor">
+ </constant>
+ <constant name="TileSet::CELL_NEIGHBOR_RIGHT_CORNER" value="1" enum="CellNeighbor">
+ </constant>
+ <constant name="TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE" value="2" enum="CellNeighbor">
+ </constant>
+ <constant name="TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER" value="3" enum="CellNeighbor">
+ </constant>
+ <constant name="TileSet::CELL_NEIGHBOR_BOTTOM_SIDE" value="4" enum="CellNeighbor">
</constant>
- <constant name="BITMASK_3X3_MINIMAL" value="1" enum="BitmaskMode">
+ <constant name="TileSet::CELL_NEIGHBOR_BOTTOM_CORNER" value="5" enum="CellNeighbor">
</constant>
- <constant name="BITMASK_3X3" value="2" enum="BitmaskMode">
+ <constant name="TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE" value="6" enum="CellNeighbor">
</constant>
- <constant name="BIND_TOPLEFT" value="1" enum="AutotileBindings">
+ <constant name="TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER" value="7" enum="CellNeighbor">
</constant>
- <constant name="BIND_TOP" value="2" enum="AutotileBindings">
+ <constant name="TileSet::CELL_NEIGHBOR_LEFT_SIDE" value="8" enum="CellNeighbor">
</constant>
- <constant name="BIND_TOPRIGHT" value="4" enum="AutotileBindings">
+ <constant name="TileSet::CELL_NEIGHBOR_LEFT_CORNER" value="9" enum="CellNeighbor">
</constant>
- <constant name="BIND_LEFT" value="8" enum="AutotileBindings">
+ <constant name="TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE" value="10" enum="CellNeighbor">
</constant>
- <constant name="BIND_CENTER" value="16" enum="AutotileBindings">
+ <constant name="TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER" value="11" enum="CellNeighbor">
</constant>
- <constant name="BIND_RIGHT" value="32" enum="AutotileBindings">
+ <constant name="TileSet::CELL_NEIGHBOR_TOP_SIDE" value="12" enum="CellNeighbor">
</constant>
- <constant name="BIND_BOTTOMLEFT" value="64" enum="AutotileBindings">
+ <constant name="TileSet::CELL_NEIGHBOR_TOP_CORNER" value="13" enum="CellNeighbor">
</constant>
- <constant name="BIND_BOTTOM" value="128" enum="AutotileBindings">
+ <constant name="TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE" value="14" enum="CellNeighbor">
</constant>
- <constant name="BIND_BOTTOMRIGHT" value="256" enum="AutotileBindings">
+ <constant name="TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER" value="15" enum="CellNeighbor">
</constant>
- <constant name="SINGLE_TILE" value="0" enum="TileMode">
+ <constant name="TERRAIN_MODE_MATCH_CORNERS_AND_SIDES" value="0" enum="TerrainMode">
</constant>
- <constant name="AUTO_TILE" value="1" enum="TileMode">
+ <constant name="TERRAIN_MODE_MATCH_CORNERS" value="1" enum="TerrainMode">
</constant>
- <constant name="ATLAS_TILE" value="2" enum="TileMode">
+ <constant name="TERRAIN_MODE_MATCH_SIDES" value="2" enum="TerrainMode">
</constant>
</constants>
</class>
diff --git a/doc/classes/TileSetAtlasSource.xml b/doc/classes/TileSetAtlasSource.xml
new file mode 100644
index 0000000000..a7a304ca27
--- /dev/null
+++ b/doc/classes/TileSetAtlasSource.xml
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="TileSetAtlasSource" inherits="TileSetSource" version="4.0">
+ <brief_description>
+ </brief_description>
+ <description>
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="can_move_tile_in_atlas" qualifiers="const">
+ <return type="bool">
+ </return>
+ <argument index="0" name="atlas_coords" type="Vector2i">
+ </argument>
+ <argument index="1" name="new_atlas_coords" type="Vector2i" default="Vector2i( -1, -1 )">
+ </argument>
+ <argument index="2" name="new_size" type="Vector2i" default="Vector2i( -1, -1 )">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="clear_tiles_outside_texture">
+ <return type="void">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="create_alternative_tile">
+ <return type="int">
+ </return>
+ <argument index="0" name="atlas_coords" type="Vector2i">
+ </argument>
+ <argument index="1" name="alternative_id_override" type="int" default="-1">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="create_tile">
+ <return type="void">
+ </return>
+ <argument index="0" name="atlas_coords" type="Vector2i">
+ </argument>
+ <argument index="1" name="size" type="Vector2i" default="Vector2i( 1, 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_atlas_grid_size" qualifiers="const">
+ <return type="Vector2i">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="get_next_alternative_tile_id" qualifiers="const">
+ <return type="int">
+ </return>
+ <argument index="0" name="atlas_coords" type="Vector2i">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="get_tile_at_coords" qualifiers="const">
+ <return type="Vector2i">
+ </return>
+ <argument index="0" name="atlas_coords" type="Vector2i">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="get_tile_data" qualifiers="const">
+ <return type="Object">
+ </return>
+ <argument index="0" name="atlas_coords" type="Vector2i">
+ </argument>
+ <argument index="1" name="index" type="int">
+ </argument>
+ <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_tile_size_in_atlas" qualifiers="const">
+ <return type="Vector2i">
+ </return>
+ <argument index="0" name="atlas_coords" type="Vector2i">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="get_tile_texture_region" qualifiers="const">
+ <return type="Rect2i">
+ </return>
+ <argument index="0" name="atlas_coords" type="Vector2i">
+ </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_tile" qualifiers="const">
+ <return type="bool">
+ </return>
+ <argument index="0" name="atlas_coords" type="Vector2i">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="has_tiles_outside_texture">
+ <return type="bool">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="move_tile_in_atlas">
+ <return type="void">
+ </return>
+ <argument index="0" name="atlas_coords" type="Vector2i">
+ </argument>
+ <argument index="1" name="new_atlas_coords" type="Vector2i" default="Vector2i( -1, -1 )">
+ </argument>
+ <argument index="2" name="new_size" type="Vector2i" default="Vector2i( -1, -1 )">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="remove_alternative_tile">
+ <return type="void">
+ </return>
+ <argument index="0" name="atlas_coords" type="Vector2i">
+ </argument>
+ <argument index="1" name="alternative_tile" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="remove_tile">
+ <return type="void">
+ </return>
+ <argument index="0" name="atlas_coords" type="Vector2i">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="set_alternative_tile_id">
+ <return type="void">
+ </return>
+ <argument index="0" name="atlas_coords" type="Vector2i">
+ </argument>
+ <argument index="1" name="alternative_tile" type="int">
+ </argument>
+ <argument index="2" name="new_id" type="int">
+ </argument>
+ <description>
+ </description>
+ </method>
+ </methods>
+ <members>
+ <member name="margins" type="Vector2i" setter="set_margins" getter="get_margins" default="Vector2i( 0, 0 )">
+ </member>
+ <member name="separation" type="Vector2i" setter="set_separation" getter="get_separation" default="Vector2i( 0, 0 )">
+ </member>
+ <member name="texture" type="Texture2D" setter="set_texture" getter="get_texture">
+ </member>
+ <member name="tile_size" type="Vector2i" setter="set_texture_region_size" getter="get_texture_region_size" default="Vector2i( 16, 16 )">
+ </member>
+ </members>
+ <constants>
+ </constants>
+</class>
diff --git a/doc/classes/TileSetSource.xml b/doc/classes/TileSetSource.xml
new file mode 100644
index 0000000000..6a3029bb3f
--- /dev/null
+++ b/doc/classes/TileSetSource.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="TileSetSource" inherits="Resource" version="4.0">
+ <brief_description>
+ </brief_description>
+ <description>
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ </methods>
+ <constants>
+ </constants>
+</class>
diff --git a/drivers/dummy/rasterizer_dummy.h b/drivers/dummy/rasterizer_dummy.h
index 64582eb784..1a27c54757 100644
--- a/drivers/dummy/rasterizer_dummy.h
+++ b/drivers/dummy/rasterizer_dummy.h
@@ -567,10 +567,17 @@ public:
void particles_set_use_local_coordinates(RID p_particles, bool p_enable) override {}
void particles_set_process_material(RID p_particles, RID p_material) override {}
void particles_set_fixed_fps(RID p_particles, int p_fps) override {}
+ void particles_set_interpolate(RID p_particles, bool p_enable) override {}
void particles_set_fractional_delta(RID p_particles, bool p_enable) override {}
void particles_set_subemitter(RID p_particles, RID p_subemitter_particles) override {}
- void particles_set_view_axis(RID p_particles, const Vector3 &p_axis) override {}
+ void particles_set_view_axis(RID p_particles, const Vector3 &p_axis, const Vector3 &p_up_axis) override {}
void particles_set_collision_base_size(RID p_particles, float p_size) override {}
+
+ void particles_set_transform_align(RID p_particles, RS::ParticlesTransformAlign p_transform_align) override {}
+
+ void particles_set_trails(RID p_particles, bool p_enable, float p_length) override {}
+ void particles_set_trail_bind_poses(RID p_particles, const Vector<Transform> &p_bind_poses) override {}
+
void particles_restart(RID p_particles) override {}
void particles_set_draw_order(RID p_particles, RS::ParticlesDrawOrder p_order) override {}
diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp
index 12a67c0e07..4649cee17f 100644
--- a/drivers/vulkan/vulkan_context.cpp
+++ b/drivers/vulkan/vulkan_context.cpp
@@ -1656,13 +1656,13 @@ Error VulkanContext::prepare_buffers() {
if (err == VK_ERROR_OUT_OF_DATE_KHR) {
// swapchain is out of date (e.g. the window was resized) and
// must be recreated:
- print_line("early out of data");
+ print_verbose("Vulkan: Early out of date swapchain, recreating.");
//resize_notify();
_update_swap_chain(w);
} else if (err == VK_SUBOPTIMAL_KHR) {
- print_line("early suboptimal");
// swapchain is not as optimal as it could be, but the platform's
// presentation engine will still present the image correctly.
+ print_verbose("Vulkan: Early suboptimal swapchain.");
break;
} else {
ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
@@ -1870,12 +1870,12 @@ Error VulkanContext::swap_buffers() {
if (err == VK_ERROR_OUT_OF_DATE_KHR) {
// swapchain is out of date (e.g. the window was resized) and
// must be recreated:
- print_line("out of date");
+ print_verbose("Vulkan: Swapchain is out of date, recreating.");
resize_notify();
} else if (err == VK_SUBOPTIMAL_KHR) {
// swapchain is not as optimal as it could be, but the platform's
// presentation engine will still present the image correctly.
- print_line("suboptimal");
+ print_verbose("Vulkan: Swapchain is suboptimal.");
} else {
ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
}
@@ -1971,13 +1971,13 @@ void VulkanContext::local_device_push_command_buffers(RID p_local_device, const
VkResult err = vkQueueSubmit(ld->queue, 1, &submit_info, VK_NULL_HANDLE);
if (err == VK_ERROR_OUT_OF_HOST_MEMORY) {
- print_line("out of host memory");
+ print_line("Vulkan: Out of host memory!");
}
if (err == VK_ERROR_OUT_OF_DEVICE_MEMORY) {
- print_line("out of device memory");
+ print_line("Vulkan: Out of device memory!");
}
if (err == VK_ERROR_DEVICE_LOST) {
- print_line("device lost");
+ print_line("Vulkan: Device lost!");
}
ERR_FAIL_COND(err);
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 6390755656..7aed5b2b7f 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -170,8 +170,7 @@
#include "editor/plugins/texture_layered_editor_plugin.h"
#include "editor/plugins/texture_region_editor_plugin.h"
#include "editor/plugins/theme_editor_plugin.h"
-#include "editor/plugins/tile_map_editor_plugin.h"
-#include "editor/plugins/tile_set_editor_plugin.h"
+#include "editor/plugins/tiles/tiles_editor_plugin.h"
#include "editor/plugins/version_control_editor_plugin.h"
#include "editor/plugins/visual_shader_editor_plugin.h"
#include "editor/progress_dialog.h"
@@ -1804,28 +1803,6 @@ void EditorNode::_dialog_action(String p_file) {
}
} break;
- case FILE_EXPORT_TILESET: {
- Ref<TileSet> tileset;
- if (FileAccess::exists(p_file) && file_export_lib_merge->is_pressed()) {
- tileset = ResourceLoader::load(p_file, "TileSet");
-
- if (tileset.is_null()) {
- show_accept(TTR("Can't load TileSet for merging!"), TTR("OK"));
- return;
- }
-
- } else {
- tileset = Ref<TileSet>(memnew(TileSet));
- }
-
- TileSetEditor::update_library_file(editor_data.get_edited_scene_root(), tileset, true);
-
- Error err = ResourceSaver::save(p_file, tileset);
- if (err) {
- show_accept(TTR("Error saving TileSet!"), TTR("OK"));
- return;
- }
- } break;
case RESOURCE_SAVE:
case RESOURCE_SAVE_AS: {
@@ -2470,15 +2447,24 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
Node *scene = editor_data.get_edited_scene_root(scene_idx);
if (!scene) {
- int saved = _save_external_resources();
- String err_text;
+ if (p_option == FILE_SAVE_SCENE) {
+ // Pressing Ctrl + S saves the current script if a scene is currently open, but it won't if the scene has no root node.
+ // Work around this by explicitly saving the script in this case (similar to pressing Ctrl + Alt + S).
+ ScriptEditor::get_singleton()->save_current_script();
+ }
+
+ const int saved = _save_external_resources();
if (saved > 0) {
- err_text = vformat(TTR("Saved %s modified resource(s)."), itos(saved));
- } else {
- err_text = TTR("A root node is required to save the scene.");
+ show_accept(
+ vformat(TTR("The current scene has no root node, but %d modified external resource(s) were saved anyway."), saved),
+ TTR("OK"));
+ } else if (p_option == FILE_SAVE_AS_SCENE) {
+ // Don't show this dialog when pressing Ctrl + S to avoid interfering with script saving.
+ show_accept(
+ TTR("A root node is required to save the scene. You can add a root node using the Scene tree dock."),
+ TTR("OK"));
}
- show_accept(err_text, TTR("OK"));
break;
}
@@ -2540,25 +2526,6 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
file_export_lib->set_title(TTR("Export Mesh Library"));
} break;
- case FILE_EXPORT_TILESET: {
- //Make sure that the scene has a root before trying to convert to tileset
- if (!editor_data.get_edited_scene_root()) {
- show_accept(TTR("This operation can't be done without a root node."), TTR("OK"));
- break;
- }
-
- List<String> extensions;
- Ref<TileSet> ml(memnew(TileSet));
- ResourceSaver::get_recognized_extensions(ml, &extensions);
- file_export_lib->clear_filters();
- for (List<String>::Element *E = extensions.front(); E; E = E->next()) {
- file_export_lib->add_filter("*." + E->get());
- }
-
- file_export_lib->popup_file_dialog();
- file_export_lib->set_title(TTR("Export Tile Set"));
-
- } break;
case FILE_EXTERNAL_OPEN_SCENE: {
if (unsaved_cache && !p_confirmed) {
@@ -5902,7 +5869,7 @@ EditorNode::EditorNode() {
EDITOR_DEF("interface/inspector/horizontal_vector2_editing", false);
EDITOR_DEF("interface/inspector/horizontal_vector_types_editing", true);
EDITOR_DEF("interface/inspector/open_resources_in_current_inspector", true);
- EDITOR_DEF("interface/inspector/resources_to_open_in_new_inspector", "Script,MeshLibrary,TileSet");
+ EDITOR_DEF("interface/inspector/resources_to_open_in_new_inspector", "Script,MeshLibrary");
EDITOR_DEF("interface/inspector/default_color_picker_mode", 0);
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "interface/inspector/default_color_picker_mode", PROPERTY_HINT_ENUM, "RGB,HSV,RAW", PROPERTY_USAGE_DEFAULT));
EDITOR_DEF("interface/inspector/default_color_picker_shape", (int32_t)ColorPicker::SHAPE_VHS_CIRCLE);
@@ -6231,8 +6198,8 @@ EditorNode::EditorNode() {
p = file_menu->get_popup();
- p->add_shortcut(ED_SHORTCUT("editor/new_scene", TTR("New Scene")), FILE_NEW_SCENE);
- p->add_shortcut(ED_SHORTCUT("editor/new_inherited_scene", TTR("New Inherited Scene...")), FILE_NEW_INHERITED_SCENE);
+ p->add_shortcut(ED_SHORTCUT("editor/new_scene", TTR("New Scene"), KEY_MASK_CMD + KEY_N), FILE_NEW_SCENE);
+ p->add_shortcut(ED_SHORTCUT("editor/new_inherited_scene", TTR("New Inherited Scene..."), KEY_MASK_CMD + KEY_MASK_SHIFT + KEY_N), FILE_NEW_INHERITED_SCENE);
p->add_shortcut(ED_SHORTCUT("editor/open_scene", TTR("Open Scene..."), KEY_MASK_CMD + KEY_O), FILE_OPEN_SCENE);
p->add_shortcut(ED_SHORTCUT("editor/reopen_closed_scene", TTR("Reopen Closed Scene"), KEY_MASK_CMD + KEY_MASK_SHIFT + KEY_T), FILE_OPEN_PREV);
p->add_submenu_item(TTR("Open Recent"), "RecentScenes", FILE_OPEN_RECENT);
@@ -6254,7 +6221,6 @@ EditorNode::EditorNode() {
p->add_child(pm_export);
p->add_submenu_item(TTR("Convert To..."), "Export");
pm_export->add_shortcut(ED_SHORTCUT("editor/convert_to_MeshLibrary", TTR("MeshLibrary...")), FILE_EXPORT_MESH_LIBRARY);
- pm_export->add_shortcut(ED_SHORTCUT("editor/convert_to_TileSet", TTR("TileSet...")), FILE_EXPORT_TILESET);
pm_export->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
p->add_separator();
@@ -6826,8 +6792,7 @@ EditorNode::EditorNode() {
add_editor_plugin(memnew(ItemListEditorPlugin(this)));
add_editor_plugin(memnew(Polygon3DEditorPlugin(this)));
add_editor_plugin(memnew(CollisionPolygon2DEditorPlugin(this)));
- add_editor_plugin(memnew(TileSetEditorPlugin(this)));
- add_editor_plugin(memnew(TileMapEditorPlugin(this)));
+ add_editor_plugin(memnew(TilesEditorPlugin(this)));
add_editor_plugin(memnew(SpriteFramesEditorPlugin(this)));
add_editor_plugin(memnew(TextureRegionEditorPlugin(this)));
add_editor_plugin(memnew(GIProbeEditorPlugin(this)));
diff --git a/editor/editor_node.h b/editor/editor_node.h
index d06851cb4f..07de183719 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -135,7 +135,6 @@ private:
FILE_EXPORT_MESH_LIBRARY,
FILE_INSTALL_ANDROID_SOURCE,
FILE_EXPLORE_ANDROID_BUILD_TEMPLATES,
- FILE_EXPORT_TILESET,
FILE_SAVE_OPTIMIZED,
FILE_OPEN_RECENT,
FILE_OPEN_OLD_SCENE,
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index 47c0e31da6..ee55ec4c0b 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -951,7 +951,7 @@ void EditorPropertyEasing::_drag_easing(const Ref<InputEvent> &p_ev) {
const Ref<InputEventMouseMotion> mm = p_ev;
- if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) {
+ if (dragging && mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) {
float rel = mm->get_relative().x;
if (rel == 0) {
return;
@@ -3254,6 +3254,8 @@ EditorPropertyResource::EditorPropertyResource() {
preview->set_offset(SIDE_TOP, 1);
preview->set_offset(SIDE_BOTTOM, -1);
preview->set_offset(SIDE_RIGHT, -1);
+ // This is required to draw the focus outline in front of the preview, rather than behind.
+ preview->set_draw_behind_parent(true);
assign->add_child(preview);
assign->connect("gui_input", callable_mp(this, &EditorPropertyResource::_button_input));
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index fdca508c9e..aba14df812 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -444,8 +444,8 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
hints["interface/theme/base_color"] = PropertyInfo(Variant::COLOR, "interface/theme/base_color", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT);
_initial_set("interface/theme/accent_color", Color(0.41, 0.61, 0.91));
hints["interface/theme/accent_color"] = PropertyInfo(Variant::COLOR, "interface/theme/accent_color", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT);
- _initial_set("interface/theme/contrast", 0.25);
- hints["interface/theme/contrast"] = PropertyInfo(Variant::FLOAT, "interface/theme/contrast", PROPERTY_HINT_RANGE, "0.01, 1, 0.01");
+ _initial_set("interface/theme/contrast", 0.3);
+ hints["interface/theme/contrast"] = PropertyInfo(Variant::FLOAT, "interface/theme/contrast", PROPERTY_HINT_RANGE, "-1, 1, 0.01");
_initial_set("interface/theme/icon_saturation", 1.0);
hints["interface/theme/icon_saturation"] = PropertyInfo(Variant::FLOAT, "interface/theme/icon_saturation", PROPERTY_HINT_RANGE, "0,2,0.01", PROPERTY_USAGE_DEFAULT);
_initial_set("interface/theme/relationship_line_opacity", 0.1);
diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp
index d5ad638436..2c4f4e2973 100644
--- a/editor/editor_themes.cpp
+++ b/editor/editor_themes.cpp
@@ -560,9 +560,11 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
style_tab_unselected->set_bg_color(dark_color_1);
style_tab_unselected->set_expand_margin_size(SIDE_BOTTOM, 0);
// Add some spacing between unselected tabs to make them easier to distinguish from each other
- style_tab_unselected->set_border_color(dark_color_2);
- style_tab_unselected->set_border_width(SIDE_LEFT, Math::round(2 * EDSCALE));
- style_tab_unselected->set_border_width(SIDE_RIGHT, Math::round(2 * EDSCALE));
+ style_tab_unselected->set_border_color(Color(0, 0, 0, 0));
+ style_tab_unselected->set_border_width(SIDE_LEFT, Math::round(1 * EDSCALE));
+ style_tab_unselected->set_border_width(SIDE_RIGHT, Math::round(1 * EDSCALE));
+ style_tab_unselected->set_default_margin(SIDE_LEFT, widget_default_margin.x + 2 * EDSCALE);
+ style_tab_unselected->set_default_margin(SIDE_RIGHT, widget_default_margin.x + 2 * EDSCALE);
Ref<StyleBoxFlat> style_tab_disabled = style_tab_selected->duplicate();
style_tab_disabled->set_bg_color(disabled_bg_color);
@@ -597,15 +599,6 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_stylebox("focus", "MenuButton", style_menu);
theme->set_stylebox("disabled", "MenuButton", style_menu);
- theme->set_stylebox("normal", "PopupMenu", style_menu);
-
- Ref<StyleBoxFlat> style_menu_hover = style_widget_hover->duplicate();
- // Don't use rounded corners for hover highlights since the StyleBox touches the PopupMenu's edges.
- style_menu_hover->set_corner_radius_all(0);
- theme->set_stylebox("hover", "PopupMenu", style_menu_hover);
- theme->set_stylebox("focus", "PopupMenu", style_menu);
- theme->set_stylebox("disabled", "PopupMenu", style_menu);
-
theme->set_color("font_color", "MenuButton", font_color);
theme->set_color("font_hover_color", "MenuButton", font_hover_color);
@@ -711,8 +704,16 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
style_popup_menu->set_default_margin(SIDE_TOP, popup_menu_margin_size);
style_popup_menu->set_default_margin(SIDE_RIGHT, 1 * EDSCALE);
style_popup_menu->set_default_margin(SIDE_BOTTOM, popup_menu_margin_size);
-
+ // Always display a border for PopupMenus so they can be distinguished from their background.
+ style_popup_menu->set_border_width_all(1 * EDSCALE);
+ style_popup_menu->set_border_color(dark_color_2);
theme->set_stylebox("panel", "PopupMenu", style_popup_menu);
+
+ Ref<StyleBoxFlat> style_menu_hover = style_widget_hover->duplicate();
+ // Don't use rounded corners for hover highlights since the StyleBox touches the PopupMenu's edges.
+ style_menu_hover->set_corner_radius_all(0);
+ theme->set_stylebox("hover", "PopupMenu", style_menu_hover);
+
theme->set_stylebox("separator", "PopupMenu", style_popup_separator);
theme->set_stylebox("labeled_separator_left", "PopupMenu", style_popup_labeled_separator_left);
theme->set_stylebox("labeled_separator_right", "PopupMenu", style_popup_labeled_separator_right);
@@ -929,6 +930,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
style_content_panel->set_default_margin(SIDE_RIGHT, margin_size_extra * EDSCALE);
style_content_panel->set_default_margin(SIDE_BOTTOM, margin_size_extra * EDSCALE);
style_content_panel->set_default_margin(SIDE_LEFT, margin_size_extra * EDSCALE);
+ // Display border to visually split the body of the container from its possible backgrounds.
+ style_content_panel->set_border_width(Side::SIDE_TOP, Math::round(2 * EDSCALE));
+ style_content_panel->set_border_color(dark_color_2);
+ theme->set_stylebox("panel", "TabContainer", style_content_panel);
// this is the stylebox used in 3d and 2d viewports (no borders)
Ref<StyleBoxFlat> style_content_panel_vp = style_content_panel->duplicate();
@@ -936,9 +941,17 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
style_content_panel_vp->set_default_margin(SIDE_TOP, default_margin_size * EDSCALE);
style_content_panel_vp->set_default_margin(SIDE_RIGHT, border_width * 2);
style_content_panel_vp->set_default_margin(SIDE_BOTTOM, border_width * 2);
- theme->set_stylebox("panel", "TabContainer", style_content_panel);
theme->set_stylebox("Content", "EditorStyles", style_content_panel_vp);
+ // These styleboxes can be used on tabs against the base color background (e.g. nested tabs).
+ Ref<StyleBoxFlat> style_tab_selected_odd = style_tab_selected->duplicate();
+ style_tab_selected_odd->set_bg_color(disabled_bg_color);
+ theme->set_stylebox("tab_selected_odd", "TabContainer", style_tab_selected_odd);
+
+ Ref<StyleBoxFlat> style_content_panel_odd = style_content_panel->duplicate();
+ style_content_panel_odd->set_bg_color(disabled_bg_color);
+ theme->set_stylebox("panel_odd", "TabContainer", style_content_panel_odd);
+
// Separators
theme->set_stylebox("separator", "HSeparator", make_line_stylebox(separator_color, MAX(Math::round(EDSCALE), border_width)));
theme->set_stylebox("separator", "VSeparator", make_line_stylebox(separator_color, MAX(Math::round(EDSCALE), border_width), 0, 0, true));
diff --git a/editor/editor_zoom_widget.cpp b/editor/editor_zoom_widget.cpp
new file mode 100644
index 0000000000..f9be829493
--- /dev/null
+++ b/editor/editor_zoom_widget.cpp
@@ -0,0 +1,163 @@
+/*************************************************************************/
+/* editor_zoom_widget.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 "editor_zoom_widget.h"
+
+#include "core/os/keyboard.h"
+#include "editor/editor_scale.h"
+#include "editor/editor_settings.h"
+
+void EditorZoomWidget::_update_zoom_label() {
+ String zoom_text;
+ // The zoom level displayed is relative to the editor scale
+ // (like in most image editors). Its lower bound is clamped to 1 as some people
+ // lower the editor scale to increase the available real estate,
+ // even if their display doesn't have a particularly low DPI.
+ if (zoom >= 10) {
+ // Don't show a decimal when the zoom level is higher than 1000 %.
+ zoom_text = TS->format_number(rtos(Math::round((zoom / MAX(1, EDSCALE)) * 100))) + " " + TS->percent_sign();
+ } else {
+ zoom_text = TS->format_number(rtos(Math::snapped((zoom / MAX(1, EDSCALE)) * 100, 0.1))) + " " + TS->percent_sign();
+ }
+
+ zoom_reset->set_text(zoom_text);
+}
+
+void EditorZoomWidget::_button_zoom_minus() {
+ set_zoom_by_increments(-6);
+ emit_signal("zoom_changed", zoom);
+}
+
+void EditorZoomWidget::_button_zoom_reset() {
+ set_zoom(1.0);
+ emit_signal("zoom_changed", zoom);
+}
+
+void EditorZoomWidget::_button_zoom_plus() {
+ set_zoom_by_increments(6);
+ emit_signal("zoom_changed", zoom);
+}
+
+float EditorZoomWidget::get_zoom() {
+ return zoom;
+}
+
+void EditorZoomWidget::set_zoom(float p_zoom) {
+ if (p_zoom > 0 && p_zoom != zoom) {
+ zoom = p_zoom;
+ _update_zoom_label();
+ }
+}
+
+void EditorZoomWidget::set_zoom_by_increments(int p_increment_count) {
+ // Base increment factor defined as the twelveth root of two.
+ // This allow a smooth geometric evolution of the zoom, with the advantage of
+ // visiting all integer power of two scale factors.
+ // note: this is analogous to the 'semitones' interval in the music world
+ // In order to avoid numerical imprecisions, we compute and edit a zoom index
+ // with the following relation: zoom = 2 ^ (index / 12)
+
+ if (zoom < CMP_EPSILON || p_increment_count == 0) {
+ return;
+ }
+
+ // Remove Editor scale from the index computation
+ float zoom_noscale = zoom / MAX(1, EDSCALE);
+
+ // zoom = 2**(index/12) => log2(zoom) = index/12
+ float closest_zoom_index = Math::round(Math::log(zoom_noscale) * 12.f / Math::log(2.f));
+
+ float new_zoom_index = closest_zoom_index + p_increment_count;
+ float new_zoom = Math::pow(2.f, new_zoom_index / 12.f);
+
+ // Restore Editor scale transformation
+ new_zoom *= MAX(1, EDSCALE);
+
+ set_zoom(new_zoom);
+}
+
+void EditorZoomWidget::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED:
+ zoom_minus->set_icon(get_theme_icon("ZoomLess", "EditorIcons"));
+ zoom_plus->set_icon(get_theme_icon("ZoomMore", "EditorIcons"));
+ break;
+ default:
+ break;
+ }
+}
+
+void EditorZoomWidget::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_zoom", "zoom"), &EditorZoomWidget::set_zoom);
+ ClassDB::bind_method(D_METHOD("get_zoom"), &EditorZoomWidget::get_zoom);
+ ClassDB::bind_method(D_METHOD("set_zoom_by_increments", "increment"), &EditorZoomWidget::set_zoom_by_increments);
+
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "zoom"), "set_zoom", "get_zoom");
+
+ ADD_SIGNAL(MethodInfo("zoom_changed", PropertyInfo(Variant::FLOAT, "zoom")));
+}
+
+EditorZoomWidget::EditorZoomWidget() {
+ // Zoom buttons
+ zoom_minus = memnew(Button);
+ zoom_minus->set_flat(true);
+ add_child(zoom_minus);
+ zoom_minus->connect("pressed", callable_mp(this, &EditorZoomWidget::_button_zoom_minus));
+ zoom_minus->set_shortcut(ED_SHORTCUT("canvas_item_editor/zoom_minus", TTR("Zoom Out"), KEY_MASK_CMD | KEY_MINUS));
+ zoom_minus->set_shortcut_context(this);
+ zoom_minus->set_focus_mode(FOCUS_NONE);
+
+ zoom_reset = memnew(Button);
+ zoom_reset->set_flat(true);
+ add_child(zoom_reset);
+ zoom_reset->add_theme_constant_override("outline_size", 1);
+ zoom_reset->add_theme_color_override("font_outline_color", Color(0, 0, 0));
+ zoom_reset->add_theme_color_override("font_color", Color(1, 1, 1));
+ zoom_reset->connect("pressed", callable_mp(this, &EditorZoomWidget::_button_zoom_reset));
+ zoom_reset->set_shortcut(ED_SHORTCUT("canvas_item_editor/zoom_reset", TTR("Zoom Reset"), KEY_MASK_CMD | KEY_0));
+ zoom_reset->set_shortcut_context(this);
+ zoom_reset->set_focus_mode(FOCUS_NONE);
+ zoom_reset->set_text_align(Button::TextAlign::ALIGN_CENTER);
+ // Prevent the button's size from changing when the text size changes
+ zoom_reset->set_custom_minimum_size(Size2(75 * EDSCALE, 0));
+
+ zoom_plus = memnew(Button);
+ zoom_plus->set_flat(true);
+ add_child(zoom_plus);
+ zoom_plus->connect("pressed", callable_mp(this, &EditorZoomWidget::_button_zoom_plus));
+ zoom_plus->set_shortcut(ED_SHORTCUT("canvas_item_editor/zoom_plus", TTR("Zoom In"), KEY_MASK_CMD | KEY_EQUAL)); // Usually direct access key for PLUS
+ zoom_plus->set_shortcut_context(this);
+ zoom_plus->set_focus_mode(FOCUS_NONE);
+
+ _update_zoom_label();
+
+ add_theme_constant_override("separation", Math::round(-8 * EDSCALE));
+}
diff --git a/platform/iphone/native_video_view.h b/editor/editor_zoom_widget.h
index 2df5e27fa4..4e95018e52 100644
--- a/platform/iphone/native_video_view.h
+++ b/editor/editor_zoom_widget.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* native_video_view.h */
+/* editor_zoom_widget.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,14 +28,35 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#import <UIKit/UIKit.h>
+#ifndef EDITOR_ZOOM_WIDGET_H
+#define EDITOR_ZOOM_WIDGET_H
-@interface GodotNativeVideoView : UIView
+#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
-- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack;
-- (BOOL)isVideoPlaying;
-- (void)pauseVideo;
-- (void)unpauseVideo;
-- (void)stopVideo;
+class EditorZoomWidget : public HBoxContainer {
+ GDCLASS(EditorZoomWidget, HBoxContainer);
-@end
+ Button *zoom_minus;
+ Button *zoom_reset;
+ Button *zoom_plus;
+
+ float zoom = 1.0;
+ void _update_zoom_label();
+ void _button_zoom_minus();
+ void _button_zoom_reset();
+ void _button_zoom_plus();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ EditorZoomWidget();
+
+ float get_zoom();
+ void set_zoom(float p_zoom);
+ void set_zoom_by_increments(int p_increment_count);
+};
+
+#endif // EDITOR_ZOOM_WIDGET_H
diff --git a/editor/icons/AddAtlasTile.svg b/editor/icons/AddAtlasTile.svg
deleted file mode 100644
index a6d94005a8..0000000000
--- a/editor/icons/AddAtlasTile.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1v6h-6v2h6v6h2v-6h6v-2h-6v-6z" fill="#c9cfd4"/></svg>
diff --git a/editor/icons/AddAutotile.svg b/editor/icons/AddAutotile.svg
deleted file mode 100644
index 52664b3eb6..0000000000
--- a/editor/icons/AddAutotile.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1v6h-6v2h6v6h2v-6h6v-2h-6v-6z" fill="#4490fc"/></svg>
diff --git a/editor/icons/AddSingleTile.svg b/editor/icons/AddSingleTile.svg
deleted file mode 100644
index 64bf1c99c0..0000000000
--- a/editor/icons/AddSingleTile.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1v6h-6v2h6v6h2v-6h6v-2h-6v-6z" fill="#fce844"/></svg>
diff --git a/editor/icons/CollapseTree.svg b/editor/icons/CollapseTree.svg
new file mode 100644
index 0000000000..ece9071e03
--- /dev/null
+++ b/editor/icons/CollapseTree.svg
@@ -0,0 +1 @@
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-rule="nonzero"><path d="m8 9.669-3.536 2.583h2.536v2.537h2v-2.537h2.536z"/><path d="m8 6.355-3.536-2.583h2.536v-2.537h2v2.537h2.536z"/><path d="m.704 7.085h14.591v1.831h-14.591z"/></g></svg>
diff --git a/editor/icons/EditAddRemove.svg b/editor/icons/EditAddRemove.svg
new file mode 100644
index 0000000000..307557cbfc
--- /dev/null
+++ b/editor/icons/EditAddRemove.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.5105509 1c-.554 0-1 .446-1 1v2h4v-2c0-.554-.446-1-1-1zm5.4894491 12.5v1.5h6v-1.5zm-6.4894491-8.5v7l2 3 2-3v-7zm1 1h1v5h-1zm7.7394491 0v2.25h-2.25v1.5h2.25v2.25h1.5v-2.25h2.25v-1.5h-2.25v-2.25z" fill="#e0e0e0"/></svg>
diff --git a/editor/icons/EditorHandleDisabled.svg b/editor/icons/EditorHandleDisabled.svg
new file mode 100644
index 0000000000..483a25a571
--- /dev/null
+++ b/editor/icons/EditorHandleDisabled.svg
@@ -0,0 +1 @@
+<svg height="10" viewBox="0 0 10 10" width="10" xmlns="http://www.w3.org/2000/svg"><path d="m5 0c-2.7614237 0-5 2.2385763-5 5s2.2385763 5 5 5 5-2.2385763 5-5-2.2385763-5-5-5zm-.0421327 2.2165215c.014044-.0001063.0280887-.0001063.0421327 0 1.5372727 0 2.7834785 1.2462058 2.7834785 2.7834785s-1.2462058 2.7834785-2.7834785 2.7834785-2.7834785-1.2462058-2.7834785-2.7834785c-.0001743-1.5209681 1.2205519-2.760456 2.7413458-2.7834785z" fill="#fff" fill-opacity=".294118"/></svg>
diff --git a/editor/icons/Eraser.svg b/editor/icons/Eraser.svg
new file mode 100644
index 0000000000..4995fa863c
--- /dev/null
+++ b/editor/icons/Eraser.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m10.228155 1.5447161-9.60250173 9.6107429 1.41421353 1.414213 1.6134635 1.885612.00693-.0069 4.2288056.000024 7.4852811-7.4852817zm-4.4456043 7.2823178 2.3136653 2.5858141-1.0357479 1.035746h-2.5853592l-1.0209853-1.293133z" fill="#e0e0e0" fill-opacity=".996078" stroke-width="1.02405"/></svg>
diff --git a/editor/icons/ExpandTree.svg b/editor/icons/ExpandTree.svg
new file mode 100644
index 0000000000..abdc1f9458
--- /dev/null
+++ b/editor/icons/ExpandTree.svg
@@ -0,0 +1 @@
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-rule="nonzero"><path d="m8 16-3.536-2.597h2.536v-2.523h2v2.523h2.536z"/><path d="m8 0-3.536 2.583h2.536v2.537h2v-2.537h2.536z"/><path d="m.704 7.085h14.591v1.831h-14.591z"/></g></svg>
diff --git a/editor/icons/FontSize.svg b/editor/icons/FontSize.svg
index e608d89b6a..3e148009ce 100644
--- a/editor/icons/FontSize.svg
+++ b/editor/icons/FontSize.svg
@@ -1 +1 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="SmallerT"><rect x="1.047" y="7.127" width="6.025" height="1.2" style="fill:#e0e0e0;fill-rule:nonzero;"/><rect x="3.452" y="7.127" width="1.214" height="6.508" style="fill:#e0e0e0;fill-rule:nonzero;"/><rect x="2.238" y="13.171" width="3.643" height="0.465" style="fill:#e0e0e0;fill-rule:nonzero;"/><path d="M1.477,7.127l0,2.4l-0.43,0l-0,-2.4l0.43,0Z" style="fill:#e0e0e0;fill-rule:nonzero;"/><path d="M7.071,7.127l0,2.4l-0.43,0l0,-2.4l0.43,0Z" style="fill:#e0e0e0;fill-rule:nonzero;"/><path d="M1.477,8.327l0,1.2c0,-0.658 0.389,-1.2 0.861,-1.2l-0.861,0Z" style="fill:#e0e0e0;fill-rule:nonzero;"/><path d="M5.78,8.327c0.473,0 0.861,0.542 0.861,1.2l0,-1.2l-0.861,0Z" style="fill:#e0e0e0;fill-rule:nonzero;"/><path d="M2.238,13.171c0.666,-0 1.214,-0.42 1.214,-0.93l0,0.93l-1.214,-0Z" style="fill:#e0e0e0;fill-rule:nonzero;"/><path d="M5.88,13.171c-0.666,-0 -1.214,-0.42 -1.214,-0.93l0,0.93l1.214,-0Z" style="fill:#e0e0e0;fill-rule:nonzero;"/></g><g id="BiggerT"><rect x="4.563" y="2.873" width="10.773" height="1.539" style="fill:#e0e0e0;fill-rule:nonzero;"/><rect x="9.18" y="2.873" width="1.539" height="10.773" style="fill:#e0e0e0;fill-rule:nonzero;"/><rect x="7.641" y="12.877" width="4.617" height="0.769" style="fill:#e0e0e0;fill-rule:nonzero;"/><path d="M5.332,2.873l0,3.078l-0.769,0l-0,-3.078l0.769,0Z" style="fill:#e0e0e0;fill-rule:nonzero;"/><path d="M15.336,2.873l-0,3.078l-0.77,0l0,-3.078l0.77,0Z" style="fill:#e0e0e0;fill-rule:nonzero;"/><path d="M5.332,4.412l0,1.539c0,-0.844 0.695,-1.539 1.539,-1.539l-1.539,0Z" style="fill:#e0e0e0;fill-rule:nonzero;"/><path d="M13.027,4.412c0.844,0 1.539,0.695 1.539,1.539l0,-1.539l-1.539,0Z" style="fill:#e0e0e0;fill-rule:nonzero;"/><path d="M7.641,12.877c0.844,-0 1.539,-0.695 1.539,-1.539l-0,1.539l-1.539,-0Z" style="fill:#e0e0e0;fill-rule:nonzero;"/><path d="M12.258,12.877c-0.845,-0 -1.539,-0.695 -1.539,-1.539l-0,1.539l1.539,-0Z" style="fill:#e0e0e0;fill-rule:nonzero;"/></g></svg>
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-rule="nonzero"><path d="m1.047 7.127h6.025v1.2h-6.025z"/><path d="m3.452 7.127h1.214v6.508h-1.214z"/><path d="m2.238 13.171h3.643v.465h-3.643z"/><path d="m1.477 7.127v2.4h-.43v-2.4z"/><path d="m7.071 7.127v2.4h-.43v-2.4z"/><path d="m1.477 8.327v1.2c0-.658.389-1.2.861-1.2z"/><path d="m5.78 8.327c.473 0 .861.542.861 1.2v-1.2z"/><path d="m2.238 13.171c.666 0 1.214-.42 1.214-.93v.93z"/><path d="m5.88 13.171c-.666 0-1.214-.42-1.214-.93v.93z"/><path d="m4.563 2.873h10.773v1.539h-10.773z"/><path d="m9.18 2.873h1.539v10.773h-1.539z"/><path d="m7.641 12.877h4.617v.769h-4.617z"/><path d="m5.332 2.873v3.078h-.769v-3.078z"/><path d="m15.336 2.873v3.078h-.77v-3.078z"/><path d="m5.332 4.412v1.539c0-.844.695-1.539 1.539-1.539z"/><path d="m13.027 4.412c.844 0 1.539.695 1.539 1.539v-1.539z"/><path d="m7.641 12.877c.844 0 1.539-.695 1.539-1.539v1.539z"/><path d="m12.258 12.877c-.845 0-1.539-.695-1.539-1.539v1.539z"/></g></svg>
diff --git a/editor/icons/RectangleAddRemove.svg b/editor/icons/RectangleAddRemove.svg
new file mode 100644
index 0000000000..87e2155a0d
--- /dev/null
+++ b/editor/icons/RectangleAddRemove.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m2 2c-.5522619.0000552-.9999448.4477381-1 1v10c.0000552.552262.4477381.999945 1 1h6v-2h-5v-8h10v1h2v-2c-.000055-.5522619-.447738-.9999448-1-1zm9.25 4v2.25h-2.25v1.5h2.25v2.25h1.5v-2.25h2.25v-1.5h-2.25v-2.25zm-2.25 7.5v1.5h6v-1.5z" fill="#e0e0e0"/></svg>
diff --git a/editor/icons/TerrainMatchCorners.svg b/editor/icons/TerrainMatchCorners.svg
new file mode 100644
index 0000000000..b9dfcf67d2
--- /dev/null
+++ b/editor/icons/TerrainMatchCorners.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g stroke-width="70.7093"><path d="m15 1h-6v3h3v3h3z" fill="#ffa62a"/><path d="m1 1h6v3h-2.9999996l-.0000004 3h-3.0000004z" fill="#1aab1a"/><path d="m1 15h5.9999999v-3h-3v-3h-2.9999999z" fill="#ffa62a"/><path d="m15.000001 15h-6v-3h2.999999l.000001-2.9999997h3z" fill="#1aab1a"/></g></svg>
diff --git a/editor/icons/TerrainMatchCornersAndSides.svg b/editor/icons/TerrainMatchCornersAndSides.svg
new file mode 100644
index 0000000000..81153005bd
--- /dev/null
+++ b/editor/icons/TerrainMatchCornersAndSides.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m6 1h4v3h-1l-1 1-1-1h-1z" fill="#ffa62a" stroke-width="70.7093"/><path d="m1 15h4v-3h-1v-1h-3z" fill="#1aab1a" stroke-width="70.7093"/><path d="m6 15h4v-3h-1l-1-1-1 1h-1z" fill="#ffa62a" stroke-width="99.998"/><g stroke-width="70.7093"><path d="m1 10v-4h3v1l1 1-1 1v1z" fill="#ffa62a"/><path d="m15 10v-4h-3v1l-1 1 1 1v1z" fill="#ffa62a"/><g fill="#1aab1a"><path d="m15 15h-4v-3h1v-1h3z"/><path d="m15 1h-4v3h1v1h3z"/><path d="m1 1h4.0000004v3h-1v1h-3.0000004z"/></g></g></svg>
diff --git a/editor/icons/TerrainMatchSides.svg b/editor/icons/TerrainMatchSides.svg
new file mode 100644
index 0000000000..1e2ec75ea7
--- /dev/null
+++ b/editor/icons/TerrainMatchSides.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g stroke-width="70.7093"><path d="m1 14v-12l3 3v2l1 1-1 1v2z" fill="#1aab1a"/><path d="m15 14v-12l-3 2.7057075v2l-1 1.0000001 1 1v1.9999994z" fill="#1aab1a"/><g fill="#ffa62a"><path d="m2 15h12l-3-3h-2l-1-1-1 1h-2z"/><path d="m14 1h-12l2.9999992 3h1.9999998l1.000001.9999999 1-.9999999h1.999999z"/></g></g></svg>
diff --git a/editor/icons/ThemeDeselectAll.svg b/editor/icons/ThemeDeselectAll.svg
new file mode 100644
index 0000000000..d43ca85163
--- /dev/null
+++ b/editor/icons/ThemeDeselectAll.svg
@@ -0,0 +1 @@
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0"><path d="m5.952 6.976h8.063v2.005h-8.063z" fill-rule="nonzero"/><path d="m.989 5.995h4.011v4.01h-4.011zm.968.968h2.075v2.074h-2.075z"/><path d="m5.952 1.956h8.063v2.005h-8.063z" fill-rule="nonzero"/><path d="m.989.974h4.011v4.011h-4.011zm.968.968h2.075v2.075h-2.075z"/><path d="m5.952 11.996h8.063v2.005h-8.063z" fill-rule="nonzero"/><path d="m.989 11.015h4.011v4.011h-4.011zm.968.968h2.075v2.075h-2.075z"/></g></svg>
diff --git a/editor/icons/ThemeRemoveAllItems.svg b/editor/icons/ThemeRemoveAllItems.svg
index 47ed624d04..652274a0e7 100644
--- a/editor/icons/ThemeRemoveAllItems.svg
+++ b/editor/icons/ThemeRemoveAllItems.svg
@@ -1 +1 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><path d="M8,1.745c-0.595,0 -1.084,0.489 -1.084,1.084l0,3.699l-3.851,-1.927c-0.163,-0.08 -0.343,-0.119 -0.525,-0.112c-0.395,0.015 -0.752,0.244 -0.929,0.597c-0.076,0.151 -0.115,0.317 -0.115,0.485c0,0.41 0.233,0.786 0.599,0.97l3.481,1.74l-3.481,1.74c-0.366,0.184 -0.599,0.56 -0.599,0.97c0,0.168 0.039,0.334 0.115,0.485c0.183,0.367 0.559,0.599 0.969,0.599c0.168,0 0.334,-0.039 0.485,-0.114l3.851,-1.927l0,3.111c0,0.594 0.489,1.084 1.084,1.084c0.595,-0 1.084,-0.49 1.084,-1.084l-0,-3.111l3.851,1.927c0.151,0.075 0.317,0.114 0.485,0.114c0.41,0 0.786,-0.232 0.969,-0.599c0.076,-0.151 0.115,-0.317 0.115,-0.485c-0,-0.41 -0.233,-0.786 -0.599,-0.97l-3.481,-1.74l3.481,-1.74c0.366,-0.184 0.599,-0.56 0.599,-0.97c-0,-0.168 -0.039,-0.334 -0.115,-0.485c-0.182,-0.364 -0.554,-0.596 -0.961,-0.599c-0.171,-0.001 -0.34,0.038 -0.493,0.114l-3.851,1.927l-0,-3.699c-0,-0.595 -0.489,-1.084 -1.084,-1.084Z" style="fill:#a5efac;"/><path d="M8,1.745c-0,0 -0,1.783 -0,1.783l-1.084,0l0,-0.699c0,-0.595 0.489,-1.084 1.084,-1.084Z" style="fill:#ff7070;fill-rule:nonzero;"/><path d="M1.528,5.312l2.957,-0l-1.42,-0.711c-0.163,-0.08 -0.343,-0.119 -0.525,-0.112c-0.395,0.015 -0.752,0.244 -0.929,0.597c-0.036,0.072 -0.064,0.148 -0.083,0.226Zm5.388,-1.784l1.084,0l-0,1.784l-1.084,-0l0,-1.784Z" style="fill:#ffeb70;fill-rule:nonzero;"/><path d="M6.916,5.312l1.084,-0l-0,1.783l-4.796,0l-1.109,-0.554c-0.366,-0.184 -0.599,-0.56 -0.599,-0.97c0,-0.088 0.011,-0.175 0.032,-0.259l2.957,-0l2.431,1.216l0,-1.216Z" style="fill:#9dff70;fill-rule:nonzero;"/><path d="M3.204,7.095l4.796,0l-0,1.783l-3.619,0l1.195,-0.597l-2.372,-1.186Z" style="fill:#70ffb9;fill-rule:nonzero;"/><path d="M4.381,8.878l3.619,0l-0,1.784l-1.084,-0l0,-0.628l-1.255,0.628l-4.114,-0c0.088,-0.274 0.283,-0.508 0.548,-0.641l2.286,-1.143Z" style="fill:#70deff;fill-rule:nonzero;"/><path d="M6.916,12.445l1.084,0l-0,1.784l-0,-0c-0.595,-0.001 -1.084,-0.49 -1.084,-1.084l0,-0.7Z" style="fill:#ff70ac;fill-rule:nonzero;"/><path d="M6.916,10.662l1.084,-0l-0,1.783l-1.084,0l0,-1.783Zm-1.255,-0l-4.114,-0c-0.033,0.105 -0.051,0.216 -0.051,0.329c0,0.168 0.039,0.334 0.115,0.485c0.183,0.367 0.559,0.599 0.969,0.599c0.168,0 0.334,-0.039 0.485,-0.114l2.596,-1.299Z" style="fill:#9f70ff;fill-rule:nonzero;"/></svg>
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m8 1.745c-.595 0-1.084.489-1.084 1.084v3.699l-3.851-1.927c-.163-.08-.343-.119-.525-.112-.395.015-.752.244-.929.597-.076.151-.115.317-.115.485 0 .41.233.786.599.97l3.481 1.74-3.481 1.74c-.366.184-.599.56-.599.97 0 .168.039.334.115.485.183.367.559.599.969.599.168 0 .334-.039.485-.114l3.851-1.927v3.111c0 .594.489 1.084 1.084 1.084s1.084-.49 1.084-1.084v-3.111l3.851 1.927c.151.075.317.114.485.114.41 0 .786-.232.969-.599.076-.151.115-.317.115-.485 0-.41-.233-.786-.599-.97l-3.481-1.74 3.481-1.74c.366-.184.599-.56.599-.97 0-.168-.039-.334-.115-.485-.182-.364-.554-.596-.961-.599-.171-.001-.34.038-.493.114l-3.851 1.927v-3.699c0-.595-.489-1.084-1.084-1.084z" fill="#a5efac"/><g fill-rule="nonzero"><path d="m8 1.745v1.783h-1.084v-.699c0-.595.489-1.084 1.084-1.084z" fill="#ff7070"/><path d="m1.528 5.312h2.957l-1.42-.711c-.163-.08-.343-.119-.525-.112-.395.015-.752.244-.929.597-.036.072-.064.148-.083.226zm5.388-1.784h1.084v1.784h-1.084z" fill="#ffeb70"/><path d="m6.916 5.312h1.084v1.783h-4.796l-1.109-.554c-.366-.184-.599-.56-.599-.97 0-.088.011-.175.032-.259h2.957l2.431 1.216z" fill="#9dff70"/><path d="m3.204 7.095h4.796v1.783h-3.619l1.195-.597z" fill="#70ffb9"/><path d="m4.381 8.878h3.619v1.784h-1.084v-.628l-1.255.628h-4.114c.088-.274.283-.508.548-.641z" fill="#70deff"/><path d="m6.916 12.445h1.084v1.784c-.595-.001-1.084-.49-1.084-1.084z" fill="#ff70ac"/><path d="m6.916 10.662h1.084v1.783h-1.084zm-1.255 0h-4.114c-.033.105-.051.216-.051.329 0 .168.039.334.115.485.183.367.559.599.969.599.168 0 .334-.039.485-.114z" fill="#9f70ff"/></g></svg>
diff --git a/editor/icons/ThemeRemoveCustomItems.svg b/editor/icons/ThemeRemoveCustomItems.svg
index bb8a8bd026..839f584fce 100644
--- a/editor/icons/ThemeRemoveCustomItems.svg
+++ b/editor/icons/ThemeRemoveCustomItems.svg
@@ -1 +1 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><path d="M11.299,3c0.772,0.513 1.42,1.199 1.888,2l-2.553,0c-0.706,-0.621 -1.629,-1 -2.634,-1c-1.005,0 -1.928,0.379 -2.634,1l-2.553,0c0.468,-0.801 1.116,-1.487 1.888,-2l6.598,-0Z" style="fill:#ffeb70;fill-rule:nonzero;"/><path d="M5.366,5c-0.593,0.522 -1.033,1.216 -1.238,2l-2.043,0c0.122,-0.717 0.373,-1.392 0.728,-2l2.553,-0Zm7.821,-0c0.355,0.608 0.606,1.283 0.728,2l-2.043,0c-0.205,-0.784 -0.645,-1.478 -1.238,-2l2.553,-0Z" style="fill:#9dff70;fill-rule:nonzero;"/><path d="M13.915,7c0.056,0.326 0.085,0.66 0.085,1c-0,0.34 -0.029,0.674 -0.085,1l-2.043,0c0.083,-0.32 0.128,-0.655 0.128,-1c0,-0.345 -0.045,-0.68 -0.128,-1l2.043,-0Zm-9.787,0c-0.083,0.32 -0.128,0.655 -0.128,1c0,0.345 0.045,0.68 0.128,1l-2.043,-0c-0.056,-0.326 -0.085,-0.66 -0.085,-1c0,-0.34 0.029,-0.674 0.085,-1l2.043,0Z" style="fill:#70ffb9;fill-rule:nonzero;"/><path d="M4.128,9c0.205,0.784 0.645,1.478 1.238,2l-2.553,0c-0.355,-0.608 -0.606,-1.283 -0.728,-2l2.043,0Zm9.787,0c-0.122,0.717 -0.373,1.392 -0.728,2l-2.553,0c0.593,-0.522 1.033,-1.216 1.238,-2l2.043,0Z" style="fill:#70deff;fill-rule:nonzero;"/><path d="M11.299,13l-6.598,0c0.949,0.631 2.084,1 3.299,1c1.215,0 2.35,-0.369 3.299,-1Z" style="fill:#ff70ac;fill-rule:nonzero;"/><path d="M13.187,11c-0.468,0.801 -1.116,1.487 -1.888,2l-6.598,0c-0.772,-0.513 -1.42,-1.199 -1.888,-2l2.553,0c0.706,0.621 1.629,1 2.634,1c1.005,0 1.928,-0.379 2.634,-1l2.553,0Z" style="fill:#9f70ff;fill-rule:nonzero;"/><path d="M4.701,3l6.598,0c-0.949,-0.631 -2.084,-1 -3.299,-1c-1.215,0 -2.35,0.369 -3.299,1Z" style="fill:#ff7070;fill-rule:nonzero;"/></svg>
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero"><path d="m11.299 3c.772.513 1.42 1.199 1.888 2h-2.553c-.706-.621-1.629-1-2.634-1s-1.928.379-2.634 1h-2.553c.468-.801 1.116-1.487 1.888-2z" fill="#ffeb70"/><path d="m5.366 5c-.593.522-1.033 1.216-1.238 2h-2.043c.122-.717.373-1.392.728-2zm7.821 0c.355.608.606 1.283.728 2h-2.043c-.205-.784-.645-1.478-1.238-2z" fill="#9dff70"/><path d="m13.915 7c.056.326.085.66.085 1s-.029.674-.085 1h-2.043c.083-.32.128-.655.128-1s-.045-.68-.128-1zm-9.787 0c-.083.32-.128.655-.128 1s.045.68.128 1h-2.043c-.056-.326-.085-.66-.085-1s.029-.674.085-1z" fill="#70ffb9"/><path d="m4.128 9c.205.784.645 1.478 1.238 2h-2.553c-.355-.608-.606-1.283-.728-2zm9.787 0c-.122.717-.373 1.392-.728 2h-2.553c.593-.522 1.033-1.216 1.238-2z" fill="#70deff"/><path d="m11.299 13h-6.598c.949.631 2.084 1 3.299 1s2.35-.369 3.299-1z" fill="#ff70ac"/><path d="m13.187 11c-.468.801-1.116 1.487-1.888 2h-6.598c-.772-.513-1.42-1.199-1.888-2h2.553c.706.621 1.629 1 2.634 1s1.928-.379 2.634-1z" fill="#9f70ff"/><path d="m4.701 3h6.598c-.949-.631-2.084-1-3.299-1s-2.35.369-3.299 1z" fill="#ff7070"/></g></svg>
diff --git a/editor/icons/ThemeSelectAll.svg b/editor/icons/ThemeSelectAll.svg
new file mode 100644
index 0000000000..59d9fb3387
--- /dev/null
+++ b/editor/icons/ThemeSelectAll.svg
@@ -0,0 +1 @@
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m5.952 6.976h8.049v2.005h-8.049z" fill="#e0e0e0" fill-rule="nonzero"/><path d="m.989 5.995h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m.989 5.995h4.011v4.01h-4.011zm.968.968h2.075v2.074h-2.075z" fill="#e0e0e0"/><path d="m5.952 1.956h8.049v2.005h-8.049z" fill="#e0e0e0" fill-rule="nonzero"/><path d="m.989.974h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m.989.974h4.011v4.011h-4.011zm.968.968h2.075v2.075h-2.075z" fill="#e0e0e0"/><path d="m5.952 11.996h8.049v2.005h-8.049z" fill="#e0e0e0" fill-rule="nonzero"/><path d="m.989 11.015h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m.989 11.015h4.011v4.011h-4.011zm.968.968h2.075v2.075h-2.075z" fill="#e0e0e0"/></svg>
diff --git a/editor/icons/ThemeSelectFull.svg b/editor/icons/ThemeSelectFull.svg
new file mode 100644
index 0000000000..0fabb9961a
--- /dev/null
+++ b/editor/icons/ThemeSelectFull.svg
@@ -0,0 +1 @@
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m11 6.976h3.015v2.005h-3.015z" fill="#e0e0e0" fill-rule="nonzero"/><path d="m.989 5.995h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m.989 5.995h4.011v4.01h-4.011zm.968.968h2.075v2.074h-2.075z" fill="#e0e0e0"/><path d="m11 1.956h3.015v2.005h-3.015z" fill="#e0e0e0" fill-rule="nonzero"/><path d="m.989.974h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m.989.974h4.011v4.011h-4.011zm.968.968h2.075v2.075h-2.075z" fill="#e0e0e0"/><path d="m11 11.996h3.015v2.005h-3.015z" fill="#e0e0e0" fill-rule="nonzero"/><path d="m.989 11.015h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m.989 11.015h4.011v4.011h-4.011zm.968.968h2.075v2.075h-2.075z" fill="#e0e0e0"/><path d="m5.995 5.995h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m5.995 5.995h4.01v4.01h-4.01zm.968.968h2.074v2.074h-2.074z" fill="#e0e0e0"/><path d="m5.995.974h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m5.995.974h4.01v4.011h-4.01zm.968.968h2.074v2.075h-2.074z" fill="#e0e0e0"/><path d="m5.995 11.015h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m5.995 11.015h4.01v4.011h-4.01zm.968.968h2.074v2.075h-2.074z" fill="#e0e0e0"/></svg>
diff --git a/editor/icons/WarningPattern.svg b/editor/icons/WarningPattern.svg
new file mode 100644
index 0000000000..8ef2c14041
--- /dev/null
+++ b/editor/icons/WarningPattern.svg
@@ -0,0 +1 @@
+<svg height="64" viewBox="0 0 64 64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="m0 0v8l8-8zm16 0-16 16v8l24-24zm16 0-32 32v8l40-40zm16 0-48 48v8l56-56zm16 0-64 64h8l56-56zm0 16-48 48h8l40-40zm0 16-32 32h8s24-23 24-24zm0 16-16 16h8l8-8z" fill="#fff"/></svg>
diff --git a/editor/plugins/SCsub b/editor/plugins/SCsub
index 359d04e5df..10a65b427e 100644
--- a/editor/plugins/SCsub
+++ b/editor/plugins/SCsub
@@ -3,3 +3,5 @@
Import("env")
env.add_source_files(env.editor_sources, "*.cpp")
+
+SConscript("tiles/SCsub")
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 852c615dc8..91022ccb2c 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -1296,11 +1296,11 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo
view_offset.y += int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor();
update_viewport();
} else {
- float new_zoom = _get_next_zoom_value(-1);
+ zoom_widget->set_zoom_by_increments(-1);
if (b->get_factor() != 1.f) {
- new_zoom = zoom * ((new_zoom / zoom - 1.f) * b->get_factor() + 1.f);
+ zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * b->get_factor() + 1.f));
}
- _zoom_on_position(new_zoom, b->get_position());
+ _zoom_on_position(zoom_widget->get_zoom(), b->get_position());
}
return true;
}
@@ -1311,11 +1311,11 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo
view_offset.y -= int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor();
update_viewport();
} else {
- float new_zoom = _get_next_zoom_value(1);
+ zoom_widget->set_zoom_by_increments(1);
if (b->get_factor() != 1.f) {
- new_zoom = zoom * ((new_zoom / zoom - 1.f) * b->get_factor() + 1.f);
+ zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * b->get_factor() + 1.f));
}
- _zoom_on_position(new_zoom, b->get_position());
+ _zoom_on_position(zoom_widget->get_zoom(), b->get_position());
}
return true;
}
@@ -1391,12 +1391,13 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo
// If control key pressed, then zoom instead of pan
if (pan_gesture->get_control()) {
const float factor = pan_gesture->get_delta().y;
- float new_zoom = _get_next_zoom_value(-1);
+ zoom_widget->set_zoom_by_increments(1);
if (factor != 1.f) {
- new_zoom = zoom * ((new_zoom / zoom - 1.f) * factor + 1.f);
+ zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * factor + 1.f));
}
- _zoom_on_position(new_zoom, pan_gesture->get_position());
+ _zoom_on_position(zoom_widget->get_zoom(), pan_gesture->get_position());
+
return true;
}
@@ -4219,9 +4220,6 @@ void CanvasItemEditor::_notification(int p_what) {
key_auto_insert_button->add_theme_color_override("icon_pressed_color", key_auto_color.lerp(Color(1, 0, 0), 0.55));
animation_menu->set_icon(get_theme_icon("GuiTabMenuHl", "EditorIcons"));
- zoom_minus->set_icon(get_theme_icon("ZoomLess", "EditorIcons"));
- zoom_plus->set_icon(get_theme_icon("ZoomMore", "EditorIcons"));
-
presets_menu->set_icon(get_theme_icon("ControlLayout", "EditorIcons"));
PopupMenu *p = presets_menu->get_popup();
@@ -4579,33 +4577,6 @@ void CanvasItemEditor::_set_anchors_preset(Control::LayoutPreset p_preset) {
undo_redo->commit_action();
}
-float CanvasItemEditor::_get_next_zoom_value(int p_increment_count) const {
- // Base increment factor defined as the twelveth root of two.
- // This allow a smooth geometric evolution of the zoom, with the advantage of
- // visiting all integer power of two scale factors.
- // note: this is analogous to the 'semitones' interval in the music world
- // In order to avoid numerical imprecisions, we compute and edit a zoom index
- // with the following relation: zoom = 2 ^ (index / 12)
-
- if (zoom < CMP_EPSILON || p_increment_count == 0) {
- return 1.f;
- }
-
- // Remove Editor scale from the index computation
- float zoom_noscale = zoom / MAX(1, EDSCALE);
-
- // zoom = 2**(index/12) => log2(zoom) = index/12
- float closest_zoom_index = Math::round(Math::log(zoom_noscale) * 12.f / Math::log(2.f));
-
- float new_zoom_index = closest_zoom_index + p_increment_count;
- float new_zoom = Math::pow(2.f, new_zoom_index / 12.f);
-
- // Restore Editor scale transformation
- new_zoom *= MAX(1, EDSCALE);
-
- return new_zoom;
-}
-
void CanvasItemEditor::_zoom_on_position(float p_zoom, Point2 p_position) {
p_zoom = CLAMP(p_zoom, MIN_ZOOM, MAX_ZOOM);
@@ -4630,36 +4601,12 @@ void CanvasItemEditor::_zoom_on_position(float p_zoom, Point2 p_position) {
view_offset = view_offset_int + (view_offset_frac * closest_zoom_factor).round() / closest_zoom_factor;
}
- _update_zoom_label();
+ zoom_widget->set_zoom(zoom);
update_viewport();
}
-void CanvasItemEditor::_update_zoom_label() {
- String zoom_text;
- // The zoom level displayed is relative to the editor scale
- // (like in most image editors). Its lower bound is clamped to 1 as some people
- // lower the editor scale to increase the available real estate,
- // even if their display doesn't have a particularly low DPI.
- if (zoom >= 10) {
- // Don't show a decimal when the zoom level is higher than 1000 %.
- zoom_text = TS->format_number(rtos(Math::round((zoom / MAX(1, EDSCALE)) * 100))) + " " + TS->percent_sign();
- } else {
- zoom_text = TS->format_number(rtos(Math::snapped((zoom / MAX(1, EDSCALE)) * 100, 0.1))) + " " + TS->percent_sign();
- }
-
- zoom_reset->set_text(zoom_text);
-}
-
-void CanvasItemEditor::_button_zoom_minus() {
- _zoom_on_position(_get_next_zoom_value(-6), viewport_scrollable->get_size() / 2.0);
-}
-
-void CanvasItemEditor::_button_zoom_reset() {
- _zoom_on_position(1.0 * MAX(1, EDSCALE), viewport_scrollable->get_size() / 2.0);
-}
-
-void CanvasItemEditor::_button_zoom_plus() {
- _zoom_on_position(_get_next_zoom_value(6), viewport_scrollable->get_size() / 2.0);
+void CanvasItemEditor::_update_zoom(float p_zoom) {
+ _zoom_on_position(p_zoom, viewport_scrollable->get_size() / 2.0);
}
void CanvasItemEditor::_button_toggle_smart_snap(bool p_status) {
@@ -5401,7 +5348,7 @@ void CanvasItemEditor::_focus_selection(int p_op) {
zoom = scale_x < scale_y ? scale_x : scale_y;
zoom *= 0.90;
viewport->update();
- _update_zoom_label();
+ zoom_widget->set_zoom(zoom);
call_deferred("_popup_callback", VIEW_CENTER_TO_SELECTION);
}
}
@@ -5446,7 +5393,7 @@ Dictionary CanvasItemEditor::get_state() const {
state["show_rulers"] = show_rulers;
state["show_guides"] = show_guides;
state["show_helpers"] = show_helpers;
- state["show_zoom_control"] = zoom_hb->is_visible();
+ state["show_zoom_control"] = zoom_widget->is_visible();
state["show_edit_locks"] = show_edit_locks;
state["show_transformation_gizmos"] = show_transformation_gizmos;
state["snap_rotation"] = snap_rotation;
@@ -5464,7 +5411,7 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) {
// Compensate the editor scale, so that the editor scale can be changed
// and the zoom level will still be the same (relative to the editor scale).
zoom = float(p_state["zoom"]) * MAX(1, EDSCALE);
- _update_zoom_label();
+ zoom_widget->set_zoom(zoom);
}
if (state.has("ofs")) {
@@ -5594,7 +5541,7 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) {
if (state.has("show_zoom_control")) {
// This one is not user-controllable, but instrumentable
- zoom_hb->set_visible(state["show_zoom_control"]);
+ zoom_widget->set_visible(state["show_zoom_control"]);
}
if (state.has("snap_rotation")) {
@@ -5770,11 +5717,6 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
controls_vb = memnew(VBoxContainer);
controls_vb->set_begin(Point2(5, 5));
- zoom_hb = memnew(HBoxContainer);
- // Bring the zoom percentage closer to the zoom buttons
- zoom_hb->add_theme_constant_override("separation", Math::round(-8 * EDSCALE));
- controls_vb->add_child(zoom_hb);
-
viewport = memnew(CanvasItemEditorViewport(p_editor, this));
viewport_scrollable->add_child(viewport);
viewport->set_mouse_filter(MOUSE_FILTER_PASS);
@@ -5821,35 +5763,10 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
viewport->add_child(controls_vb);
- zoom_minus = memnew(Button);
- zoom_minus->set_flat(true);
- zoom_hb->add_child(zoom_minus);
- zoom_minus->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_zoom_minus));
- zoom_minus->set_shortcut(ED_SHORTCUT("canvas_item_editor/zoom_minus", TTR("Zoom Out"), KEY_MASK_CMD | KEY_MINUS));
- zoom_minus->set_shortcut_context(this);
- zoom_minus->set_focus_mode(FOCUS_NONE);
-
- zoom_reset = memnew(Button);
- zoom_reset->set_flat(true);
- zoom_hb->add_child(zoom_reset);
- zoom_reset->add_theme_constant_override("outline_size", 1);
- zoom_reset->add_theme_color_override("font_outline_color", Color(0, 0, 0));
- zoom_reset->add_theme_color_override("font_color", Color(1, 1, 1));
- zoom_reset->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_zoom_reset));
- zoom_reset->set_shortcut(ED_SHORTCUT("canvas_item_editor/zoom_reset", TTR("Zoom Reset"), KEY_MASK_CMD | KEY_0));
- zoom_reset->set_shortcut_context(this);
- zoom_reset->set_focus_mode(FOCUS_NONE);
- zoom_reset->set_text_align(Button::TextAlign::ALIGN_CENTER);
- // Prevent the button's size from changing when the text size changes
- zoom_reset->set_custom_minimum_size(Size2(75 * EDSCALE, 0));
-
- zoom_plus = memnew(Button);
- zoom_plus->set_flat(true);
- zoom_hb->add_child(zoom_plus);
- zoom_plus->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_zoom_plus));
- zoom_plus->set_shortcut(ED_SHORTCUT("canvas_item_editor/zoom_plus", TTR("Zoom In"), KEY_MASK_CMD | KEY_EQUAL)); // Usually direct access key for PLUS
- zoom_plus->set_shortcut_context(this);
- zoom_plus->set_focus_mode(FOCUS_NONE);
+ zoom_widget = memnew(EditorZoomWidget);
+ controls_vb->add_child(zoom_widget);
+ zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE);
+ zoom_widget->connect("zoom_changed", callable_mp(this, &CanvasItemEditor::_update_zoom));
updating_scroll = false;
diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h
index 62a9b1e162..21ef3f88df 100644
--- a/editor/plugins/canvas_item_editor_plugin.h
+++ b/editor/plugins/canvas_item_editor_plugin.h
@@ -33,6 +33,7 @@
#include "editor/editor_node.h"
#include "editor/editor_plugin.h"
+#include "editor/editor_zoom_widget.h"
#include "scene/gui/box_container.h"
#include "scene/gui/check_box.h"
#include "scene/gui/label.h"
@@ -233,10 +234,6 @@ private:
VScrollBar *v_scroll;
HBoxContainer *hb;
- Button *zoom_minus;
- Button *zoom_reset;
- Button *zoom_plus;
-
Map<Control *, Timer *> popup_temporarily_timers;
Label *warning_child_of_container;
@@ -536,13 +533,9 @@ private:
void _button_toggle_anchor_mode(bool p_status);
VBoxContainer *controls_vb;
- HBoxContainer *zoom_hb;
- float _get_next_zoom_value(int p_increment_count) const;
+ EditorZoomWidget *zoom_widget;
+ void _update_zoom(float p_zoom);
void _zoom_on_position(float p_zoom, Point2 p_position = Point2());
- void _update_zoom_label();
- void _button_zoom_minus();
- void _button_zoom_reset();
- void _button_zoom_plus();
void _button_toggle_smart_snap(bool p_status);
void _button_toggle_grid_snap(bool p_status);
void _button_override_camera(bool p_pressed);
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index adfeead33b..8c4c5a3461 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -703,7 +703,7 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) {
// Do not try to save internal scripts, but prompt to save in-memory
// scripts which are not saved to disk yet (have empty path).
if (script->get_path().find("local://") == -1 && script->get_path().find("::") == -1) {
- _save_current_script();
+ save_current_script();
}
}
if (script.is_valid()) {
@@ -1101,59 +1101,6 @@ bool ScriptEditor::is_scripts_panel_toggled() {
return list_split->is_visible();
}
-void ScriptEditor::_save_current_script() {
- ScriptEditorBase *current = _get_current_editor();
-
- if (_test_script_times_on_disk()) {
- return;
- }
-
- if (trim_trailing_whitespace_on_save) {
- current->trim_trailing_whitespace();
- }
-
- current->insert_final_newline();
-
- if (convert_indent_on_save) {
- if (use_space_indentation) {
- current->convert_indent_to_spaces();
- } else {
- current->convert_indent_to_tabs();
- }
- }
-
- RES resource = current->get_edited_resource();
- Ref<TextFile> text_file = resource;
- Ref<Script> script = resource;
-
- if (text_file != nullptr) {
- current->apply_code();
- _save_text_file(text_file, text_file->get_path());
- return;
- }
-
- if (script != nullptr) {
- const Vector<DocData::ClassDoc> &documentations = script->get_documentation();
- for (int j = 0; j < documentations.size(); j++) {
- const DocData::ClassDoc &doc = documentations.get(j);
- if (EditorHelp::get_doc_data()->has_doc(doc.name)) {
- EditorHelp::get_doc_data()->remove_doc(doc.name);
- }
- }
- }
-
- editor->save_resource(resource);
-
- if (script != nullptr) {
- const Vector<DocData::ClassDoc> &documentations = script->get_documentation();
- for (int j = 0; j < documentations.size(); j++) {
- const DocData::ClassDoc &doc = documentations.get(j);
- EditorHelp::get_doc_data()->add_doc(doc);
- update_doc(doc.name);
- }
- }
-}
-
void ScriptEditor::_menu_option(int p_option) {
ScriptEditorBase *current = _get_current_editor();
switch (p_option) {
@@ -1282,7 +1229,7 @@ void ScriptEditor::_menu_option(int p_option) {
if (current) {
switch (p_option) {
case FILE_SAVE: {
- _save_current_script();
+ save_current_script();
} break;
case FILE_SAVE_AS: {
if (trim_trailing_whitespace_on_save) {
@@ -2330,6 +2277,59 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra
return true;
}
+void ScriptEditor::save_current_script() {
+ ScriptEditorBase *current = _get_current_editor();
+
+ if (_test_script_times_on_disk()) {
+ return;
+ }
+
+ if (trim_trailing_whitespace_on_save) {
+ current->trim_trailing_whitespace();
+ }
+
+ current->insert_final_newline();
+
+ if (convert_indent_on_save) {
+ if (use_space_indentation) {
+ current->convert_indent_to_spaces();
+ } else {
+ current->convert_indent_to_tabs();
+ }
+ }
+
+ RES resource = current->get_edited_resource();
+ Ref<TextFile> text_file = resource;
+ Ref<Script> script = resource;
+
+ if (text_file != nullptr) {
+ current->apply_code();
+ _save_text_file(text_file, text_file->get_path());
+ return;
+ }
+
+ if (script != nullptr) {
+ const Vector<DocData::ClassDoc> &documentations = script->get_documentation();
+ for (int j = 0; j < documentations.size(); j++) {
+ const DocData::ClassDoc &doc = documentations.get(j);
+ if (EditorHelp::get_doc_data()->has_doc(doc.name)) {
+ EditorHelp::get_doc_data()->remove_doc(doc.name);
+ }
+ }
+ }
+
+ editor->save_resource(resource);
+
+ if (script != nullptr) {
+ const Vector<DocData::ClassDoc> &documentations = script->get_documentation();
+ for (int j = 0; j < documentations.size(); j++) {
+ const DocData::ClassDoc &doc = documentations.get(j);
+ EditorHelp::get_doc_data()->add_doc(doc);
+ update_doc(doc.name);
+ }
+ }
+}
+
void ScriptEditor::save_all_scripts() {
for (int i = 0; i < tab_container->get_child_count(); i++) {
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i));
@@ -2445,7 +2445,7 @@ void ScriptEditor::_add_callback(Object *p_obj, const String &p_function, const
script_list->select(script_list->find_metadata(i));
// Save the current script so the changes can be picked up by an external editor.
- _save_current_script();
+ save_current_script();
break;
}
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index c70fd2e555..570ebfba4e 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -312,7 +312,6 @@ class ScriptEditor : public PanelContainer {
String _get_debug_tooltip(const String &p_text, Node *_se);
- void _save_current_script();
void _resave_scripts(const String &p_str);
void _reload_scripts();
@@ -464,6 +463,7 @@ public:
void get_breakpoints(List<String> *p_breakpoints);
+ void save_current_script();
void save_all_scripts();
void set_window_layout(Ref<ConfigFile> p_layout);
diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp
index c765aa0319..0f7468bead 100644
--- a/editor/plugins/theme_editor_plugin.cpp
+++ b/editor/plugins/theme_editor_plugin.cpp
@@ -34,12 +34,1187 @@
#include "core/os/keyboard.h"
#include "core/version.h"
#include "editor/editor_scale.h"
+#include "editor/progress_dialog.h"
#include "scene/gui/progress_bar.h"
+void ThemeItemImportTree::_update_items_tree() {
+ import_items_tree->clear();
+ TreeItem *root = import_items_tree->create_item();
+
+ if (base_theme.is_null()) {
+ return;
+ }
+
+ String filter_text = import_items_filter->get_text();
+
+ List<StringName> types;
+ List<StringName> names;
+ List<StringName> filtered_names;
+ base_theme->get_type_list(&types);
+ types.sort_custom<StringName::AlphCompare>();
+
+ int color_amount = 0;
+ int constant_amount = 0;
+ int font_amount = 0;
+ int font_size_amount = 0;
+ int icon_amount = 0;
+ int stylebox_amount = 0;
+
+ tree_color_items.clear();
+ tree_constant_items.clear();
+ tree_font_items.clear();
+ tree_font_size_items.clear();
+ tree_icon_items.clear();
+ tree_stylebox_items.clear();
+
+ for (List<StringName>::Element *E = types.front(); E; E = E->next()) {
+ String type_name = (String)E->get();
+
+ TreeItem *type_node = import_items_tree->create_item(root);
+ type_node->set_meta("_can_be_imported", false);
+ type_node->set_collapsed(true);
+ type_node->set_text(0, type_name);
+ type_node->set_cell_mode(IMPORT_ITEM, TreeItem::CELL_MODE_CHECK);
+ type_node->set_checked(IMPORT_ITEM, false);
+ type_node->set_editable(IMPORT_ITEM, true);
+ type_node->set_cell_mode(IMPORT_ITEM_DATA, TreeItem::CELL_MODE_CHECK);
+ type_node->set_checked(IMPORT_ITEM_DATA, false);
+ type_node->set_editable(IMPORT_ITEM_DATA, true);
+
+ bool is_matching_filter = (filter_text.is_empty() || type_name.findn(filter_text) > -1);
+ bool has_filtered_items = false;
+ bool any_checked = false;
+ bool any_checked_with_data = false;
+
+ for (int i = 0; i < Theme::DATA_TYPE_MAX; i++) {
+ Theme::DataType dt = (Theme::DataType)i;
+
+ names.clear();
+ filtered_names.clear();
+ base_theme->get_theme_item_list(dt, E->get(), &names);
+
+ bool data_type_has_filtered_items = false;
+
+ for (List<StringName>::Element *F = names.front(); F; F = F->next()) {
+ String item_name = (String)F->get();
+ bool is_item_matching_filter = (item_name.findn(filter_text) > -1);
+ if (!filter_text.is_empty() && !is_matching_filter && !is_item_matching_filter) {
+ continue;
+ }
+
+ // Only mark this if actual items match the filter and not just the type group.
+ if (!filter_text.is_empty() && is_item_matching_filter) {
+ has_filtered_items = true;
+ data_type_has_filtered_items = true;
+ }
+ filtered_names.push_back(F->get());
+ }
+
+ if (filtered_names.size() == 0) {
+ continue;
+ }
+
+ TreeItem *data_type_node = import_items_tree->create_item(type_node);
+ data_type_node->set_meta("_can_be_imported", false);
+ data_type_node->set_metadata(0, i);
+ data_type_node->set_collapsed(!data_type_has_filtered_items);
+ data_type_node->set_cell_mode(IMPORT_ITEM, TreeItem::CELL_MODE_CHECK);
+ data_type_node->set_checked(IMPORT_ITEM, false);
+ data_type_node->set_editable(IMPORT_ITEM, true);
+ data_type_node->set_cell_mode(IMPORT_ITEM_DATA, TreeItem::CELL_MODE_CHECK);
+ data_type_node->set_checked(IMPORT_ITEM_DATA, false);
+ data_type_node->set_editable(IMPORT_ITEM_DATA, true);
+
+ List<TreeItem *> *item_list;
+
+ switch (dt) {
+ case Theme::DATA_TYPE_COLOR:
+ data_type_node->set_icon(0, get_theme_icon("Color", "EditorIcons"));
+ data_type_node->set_text(0, TTR("Colors"));
+
+ item_list = &tree_color_items;
+ color_amount += filtered_names.size();
+ break;
+
+ case Theme::DATA_TYPE_CONSTANT:
+ data_type_node->set_icon(0, get_theme_icon("MemberConstant", "EditorIcons"));
+ data_type_node->set_text(0, TTR("Constants"));
+
+ item_list = &tree_constant_items;
+ constant_amount += filtered_names.size();
+ break;
+
+ case Theme::DATA_TYPE_FONT:
+ data_type_node->set_icon(0, get_theme_icon("Font", "EditorIcons"));
+ data_type_node->set_text(0, TTR("Fonts"));
+
+ item_list = &tree_font_items;
+ font_amount += filtered_names.size();
+ break;
+
+ case Theme::DATA_TYPE_FONT_SIZE:
+ data_type_node->set_icon(0, get_theme_icon("FontSize", "EditorIcons"));
+ data_type_node->set_text(0, TTR("Font Sizes"));
+
+ item_list = &tree_font_size_items;
+ font_size_amount += filtered_names.size();
+ break;
+
+ case Theme::DATA_TYPE_ICON:
+ data_type_node->set_icon(0, get_theme_icon("ImageTexture", "EditorIcons"));
+ data_type_node->set_text(0, TTR("Icons"));
+
+ item_list = &tree_icon_items;
+ icon_amount += filtered_names.size();
+ break;
+
+ case Theme::DATA_TYPE_STYLEBOX:
+ data_type_node->set_icon(0, get_theme_icon("StyleBoxFlat", "EditorIcons"));
+ data_type_node->set_text(0, TTR("Styleboxes"));
+
+ item_list = &tree_stylebox_items;
+ stylebox_amount += filtered_names.size();
+ break;
+
+ case Theme::DATA_TYPE_MAX:
+ break; // Can't happen, but silences warning.
+ }
+
+ bool data_type_any_checked = false;
+ bool data_type_any_checked_with_data = false;
+
+ filtered_names.sort_custom<StringName::AlphCompare>();
+ for (List<StringName>::Element *F = filtered_names.front(); F; F = F->next()) {
+ TreeItem *item_node = import_items_tree->create_item(data_type_node);
+ item_node->set_meta("_can_be_imported", true);
+ item_node->set_text(0, F->get());
+ item_node->set_cell_mode(IMPORT_ITEM, TreeItem::CELL_MODE_CHECK);
+ item_node->set_checked(IMPORT_ITEM, false);
+ item_node->set_editable(IMPORT_ITEM, true);
+ item_node->set_cell_mode(IMPORT_ITEM_DATA, TreeItem::CELL_MODE_CHECK);
+ item_node->set_checked(IMPORT_ITEM_DATA, false);
+ item_node->set_editable(IMPORT_ITEM_DATA, true);
+
+ _restore_selected_item(item_node);
+ if (item_node->is_checked(IMPORT_ITEM)) {
+ data_type_any_checked = true;
+ any_checked = true;
+ }
+ if (item_node->is_checked(IMPORT_ITEM_DATA)) {
+ data_type_any_checked_with_data = true;
+ any_checked_with_data = true;
+ }
+
+ item_list->push_back(item_node);
+ }
+
+ data_type_node->set_checked(IMPORT_ITEM, data_type_any_checked);
+ data_type_node->set_checked(IMPORT_ITEM_DATA, data_type_any_checked && data_type_any_checked_with_data);
+ }
+
+ // Remove the item if it doesn't match the filter in any way.
+ if (!is_matching_filter && !has_filtered_items) {
+ root->remove_child(type_node);
+ memdelete(type_node);
+ continue;
+ }
+
+ // Show one level inside of a type group if there are matches in items.
+ if (!filter_text.is_empty() && has_filtered_items) {
+ type_node->set_collapsed(false);
+ }
+
+ type_node->set_checked(IMPORT_ITEM, any_checked);
+ type_node->set_checked(IMPORT_ITEM_DATA, any_checked && any_checked_with_data);
+ }
+
+ if (color_amount > 0) {
+ Array arr;
+ arr.push_back(color_amount);
+ select_colors_label->set_text(TTRN("One color", "{num} colors", color_amount).format(arr, "{num}"));
+ select_all_colors_button->set_visible(true);
+ select_full_colors_button->set_visible(true);
+ deselect_all_colors_button->set_visible(true);
+ } else {
+ select_colors_label->set_text(TTR("No colors found."));
+ select_all_colors_button->set_visible(false);
+ select_full_colors_button->set_visible(false);
+ deselect_all_colors_button->set_visible(false);
+ }
+
+ if (constant_amount > 0) {
+ Array arr;
+ arr.push_back(constant_amount);
+ select_constants_label->set_text(TTRN("One constant", "{num} constants", constant_amount).format(arr, "{num}"));
+ select_all_constants_button->set_visible(true);
+ select_full_constants_button->set_visible(true);
+ deselect_all_constants_button->set_visible(true);
+ } else {
+ select_constants_label->set_text(TTR("No constants found."));
+ select_all_constants_button->set_visible(false);
+ select_full_constants_button->set_visible(false);
+ deselect_all_constants_button->set_visible(false);
+ }
+
+ if (font_amount > 0) {
+ Array arr;
+ arr.push_back(font_amount);
+ select_fonts_label->set_text(TTRN("One font", "{num} fonts", font_amount).format(arr, "{num}"));
+ select_all_fonts_button->set_visible(true);
+ select_full_fonts_button->set_visible(true);
+ deselect_all_fonts_button->set_visible(true);
+ } else {
+ select_fonts_label->set_text(TTR("No fonts found."));
+ select_all_fonts_button->set_visible(false);
+ select_full_fonts_button->set_visible(false);
+ deselect_all_fonts_button->set_visible(false);
+ }
+
+ if (font_size_amount > 0) {
+ Array arr;
+ arr.push_back(font_size_amount);
+ select_font_sizes_label->set_text(TTRN("One font size", "{num} font sizes", font_size_amount).format(arr, "{num}"));
+ select_all_font_sizes_button->set_visible(true);
+ select_full_font_sizes_button->set_visible(true);
+ deselect_all_font_sizes_button->set_visible(true);
+ } else {
+ select_font_sizes_label->set_text(TTR("No font sizes found."));
+ select_all_font_sizes_button->set_visible(false);
+ select_full_font_sizes_button->set_visible(false);
+ deselect_all_font_sizes_button->set_visible(false);
+ }
+
+ if (icon_amount > 0) {
+ Array arr;
+ arr.push_back(icon_amount);
+ select_icons_label->set_text(TTRN("One icon", "{num} icons", icon_amount).format(arr, "{num}"));
+ select_all_icons_button->set_visible(true);
+ select_full_icons_button->set_visible(true);
+ deselect_all_icons_button->set_visible(true);
+ select_icons_warning_hb->set_visible(true);
+ } else {
+ select_icons_label->set_text(TTR("No icons found."));
+ select_all_icons_button->set_visible(false);
+ select_full_icons_button->set_visible(false);
+ deselect_all_icons_button->set_visible(false);
+ select_icons_warning_hb->set_visible(false);
+ }
+
+ if (stylebox_amount > 0) {
+ Array arr;
+ arr.push_back(stylebox_amount);
+ select_styleboxes_label->set_text(TTRN("One stylebox", "{num} styleboxes", stylebox_amount).format(arr, "{num}"));
+ select_all_styleboxes_button->set_visible(true);
+ select_full_styleboxes_button->set_visible(true);
+ deselect_all_styleboxes_button->set_visible(true);
+ } else {
+ select_styleboxes_label->set_text(TTR("No styleboxes found."));
+ select_all_styleboxes_button->set_visible(false);
+ select_full_styleboxes_button->set_visible(false);
+ deselect_all_styleboxes_button->set_visible(false);
+ }
+}
+
+void ThemeItemImportTree::_toggle_type_items(bool p_collapse) {
+ TreeItem *root = import_items_tree->get_root();
+ if (!root) {
+ return;
+ }
+
+ TreeItem *type_node = root->get_children();
+ while (type_node) {
+ type_node->set_collapsed(p_collapse);
+ type_node = type_node->get_next();
+ }
+}
+
+void ThemeItemImportTree::_filter_text_changed(const String &p_value) {
+ _update_items_tree();
+}
+
+void ThemeItemImportTree::_store_selected_item(TreeItem *p_tree_item) {
+ if (!p_tree_item->get_meta("_can_be_imported")) {
+ return;
+ }
+
+ TreeItem *data_type_node = p_tree_item->get_parent();
+ if (!data_type_node || data_type_node == import_items_tree->get_root()) {
+ return;
+ }
+
+ TreeItem *type_node = data_type_node->get_parent();
+ if (!type_node || type_node == import_items_tree->get_root()) {
+ return;
+ }
+
+ ThemeItem ti;
+ ti.item_name = p_tree_item->get_text(0);
+ ti.data_type = (Theme::DataType)(int)data_type_node->get_metadata(0);
+ ti.type_name = type_node->get_text(0);
+
+ bool import = p_tree_item->is_checked(IMPORT_ITEM);
+ bool with_data = p_tree_item->is_checked(IMPORT_ITEM_DATA);
+
+ if (import && with_data) {
+ selected_items[ti] = SELECT_IMPORT_FULL;
+ } else if (import) {
+ selected_items[ti] = SELECT_IMPORT_DEFINITION;
+ } else {
+ selected_items.erase(ti);
+ }
+
+ _update_total_selected(ti.data_type);
+}
+
+void ThemeItemImportTree::_restore_selected_item(TreeItem *p_tree_item) {
+ if (!p_tree_item->get_meta("_can_be_imported")) {
+ return;
+ }
+
+ TreeItem *data_type_node = p_tree_item->get_parent();
+ if (!data_type_node || data_type_node == import_items_tree->get_root()) {
+ return;
+ }
+
+ TreeItem *type_node = data_type_node->get_parent();
+ if (!type_node || type_node == import_items_tree->get_root()) {
+ return;
+ }
+
+ ThemeItem ti;
+ ti.item_name = p_tree_item->get_text(0);
+ ti.data_type = (Theme::DataType)(int)data_type_node->get_metadata(0);
+ ti.type_name = type_node->get_text(0);
+
+ if (!selected_items.has(ti)) {
+ p_tree_item->set_checked(IMPORT_ITEM, false);
+ p_tree_item->set_checked(IMPORT_ITEM_DATA, false);
+ return;
+ }
+
+ if (selected_items[ti] == SELECT_IMPORT_FULL) {
+ p_tree_item->set_checked(IMPORT_ITEM, true);
+ p_tree_item->set_checked(IMPORT_ITEM_DATA, true);
+ } else if (selected_items[ti] == SELECT_IMPORT_DEFINITION) {
+ p_tree_item->set_checked(IMPORT_ITEM, true);
+ p_tree_item->set_checked(IMPORT_ITEM_DATA, false);
+ }
+}
+
+void ThemeItemImportTree::_update_total_selected(Theme::DataType p_data_type) {
+ ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds.");
+
+ Label *total_selected_items_label;
+ switch (p_data_type) {
+ case Theme::DATA_TYPE_COLOR:
+ total_selected_items_label = total_selected_colors_label;
+ break;
+
+ case Theme::DATA_TYPE_CONSTANT:
+ total_selected_items_label = total_selected_constants_label;
+ break;
+
+ case Theme::DATA_TYPE_FONT:
+ total_selected_items_label = total_selected_fonts_label;
+ break;
+
+ case Theme::DATA_TYPE_FONT_SIZE:
+ total_selected_items_label = total_selected_font_sizes_label;
+ break;
+
+ case Theme::DATA_TYPE_ICON:
+ total_selected_items_label = total_selected_icons_label;
+ break;
+
+ case Theme::DATA_TYPE_STYLEBOX:
+ total_selected_items_label = total_selected_styleboxes_label;
+ break;
+
+ case Theme::DATA_TYPE_MAX:
+ return; // Can't happen, but silences warning.
+ }
+
+ if (!total_selected_items_label) {
+ return;
+ }
+
+ int count = 0;
+ for (Map<ThemeItem, ItemCheckedState>::Element *E = selected_items.front(); E; E = E->next()) {
+ ThemeItem ti = E->key();
+ if (ti.data_type == p_data_type) {
+ count++;
+ }
+ }
+
+ if (count == 0) {
+ total_selected_items_label->hide();
+ } else {
+ Array arr;
+ arr.push_back(count);
+ total_selected_items_label->set_text(TTRN("{num} currently selected", "{num} currently selected", count).format(arr, "{num}"));
+ total_selected_items_label->show();
+ }
+}
+
+void ThemeItemImportTree::_tree_item_edited() {
+ if (updating_tree) {
+ return;
+ }
+
+ TreeItem *edited_item = import_items_tree->get_edited();
+ if (!edited_item) {
+ return;
+ }
+
+ updating_tree = true;
+
+ int edited_column = import_items_tree->get_edited_column();
+ bool is_checked = edited_item->is_checked(edited_column);
+ if (is_checked) {
+ if (edited_column == IMPORT_ITEM_DATA) {
+ edited_item->set_checked(IMPORT_ITEM, true);
+ }
+
+ _select_all_subitems(edited_item, (edited_column == IMPORT_ITEM_DATA));
+ } else {
+ if (edited_column == IMPORT_ITEM) {
+ edited_item->set_checked(IMPORT_ITEM_DATA, false);
+ }
+
+ _deselect_all_subitems(edited_item, (edited_column == IMPORT_ITEM));
+ }
+
+ _update_parent_items(edited_item);
+ _store_selected_item(edited_item);
+
+ updating_tree = false;
+}
+
+void ThemeItemImportTree::_select_all_subitems(TreeItem *p_root_item, bool p_select_with_data) {
+ TreeItem *child_item = p_root_item->get_children();
+ while (child_item) {
+ child_item->set_checked(IMPORT_ITEM, true);
+ if (p_select_with_data) {
+ child_item->set_checked(IMPORT_ITEM_DATA, true);
+ }
+ _store_selected_item(child_item);
+
+ _select_all_subitems(child_item, p_select_with_data);
+ child_item = child_item->get_next();
+ }
+}
+
+void ThemeItemImportTree::_deselect_all_subitems(TreeItem *p_root_item, bool p_deselect_completely) {
+ TreeItem *child_item = p_root_item->get_children();
+ while (child_item) {
+ child_item->set_checked(IMPORT_ITEM_DATA, false);
+ if (p_deselect_completely) {
+ child_item->set_checked(IMPORT_ITEM, false);
+ }
+ _store_selected_item(child_item);
+
+ _deselect_all_subitems(child_item, p_deselect_completely);
+ child_item = child_item->get_next();
+ }
+}
+
+void ThemeItemImportTree::_update_parent_items(TreeItem *p_root_item) {
+ TreeItem *parent_item = p_root_item->get_parent();
+ if (!parent_item) {
+ return;
+ }
+
+ bool any_checked = false;
+ bool any_checked_with_data = false;
+
+ TreeItem *child_item = parent_item->get_children();
+ while (child_item) {
+ if (child_item->is_checked(IMPORT_ITEM)) {
+ any_checked = true;
+ }
+ if (child_item->is_checked(IMPORT_ITEM_DATA)) {
+ any_checked_with_data = true;
+ }
+
+ child_item = child_item->get_next();
+ }
+
+ parent_item->set_checked(IMPORT_ITEM, any_checked);
+ parent_item->set_checked(IMPORT_ITEM_DATA, any_checked && any_checked_with_data);
+ _update_parent_items(parent_item);
+}
+
+void ThemeItemImportTree::_select_all_items_pressed() {
+ if (updating_tree) {
+ return;
+ }
+
+ updating_tree = true;
+
+ TreeItem *root = import_items_tree->get_root();
+ _select_all_subitems(root, false);
+
+ updating_tree = false;
+}
+
+void ThemeItemImportTree::_select_full_items_pressed() {
+ if (updating_tree) {
+ return;
+ }
+
+ updating_tree = true;
+
+ TreeItem *root = import_items_tree->get_root();
+ _select_all_subitems(root, true);
+
+ updating_tree = false;
+}
+
+void ThemeItemImportTree::_deselect_all_items_pressed() {
+ if (updating_tree) {
+ return;
+ }
+
+ updating_tree = true;
+
+ TreeItem *root = import_items_tree->get_root();
+ _deselect_all_subitems(root, true);
+
+ updating_tree = false;
+}
+
+void ThemeItemImportTree::_select_all_data_type_pressed(int p_data_type) {
+ ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds.");
+
+ if (updating_tree) {
+ return;
+ }
+
+ Theme::DataType data_type = (Theme::DataType)p_data_type;
+ List<TreeItem *> *item_list;
+
+ switch (data_type) {
+ case Theme::DATA_TYPE_COLOR:
+ item_list = &tree_color_items;
+ break;
+
+ case Theme::DATA_TYPE_CONSTANT:
+ item_list = &tree_constant_items;
+ break;
+
+ case Theme::DATA_TYPE_FONT:
+ item_list = &tree_font_items;
+ break;
+
+ case Theme::DATA_TYPE_FONT_SIZE:
+ item_list = &tree_font_size_items;
+ break;
+
+ case Theme::DATA_TYPE_ICON:
+ item_list = &tree_icon_items;
+ break;
+
+ case Theme::DATA_TYPE_STYLEBOX:
+ item_list = &tree_stylebox_items;
+ break;
+
+ case Theme::DATA_TYPE_MAX:
+ return; // Can't happen, but silences warning.
+ }
+
+ updating_tree = true;
+
+ for (List<TreeItem *>::Element *E = item_list->front(); E; E = E->next()) {
+ TreeItem *child_item = E->get();
+ if (!child_item) {
+ continue;
+ }
+
+ child_item->set_checked(IMPORT_ITEM, true);
+ _update_parent_items(child_item);
+ _store_selected_item(child_item);
+ }
+
+ updating_tree = false;
+}
+
+void ThemeItemImportTree::_select_full_data_type_pressed(int p_data_type) {
+ ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds.");
+
+ if (updating_tree) {
+ return;
+ }
+
+ Theme::DataType data_type = (Theme::DataType)p_data_type;
+ List<TreeItem *> *item_list;
+
+ switch (data_type) {
+ case Theme::DATA_TYPE_COLOR:
+ item_list = &tree_color_items;
+ break;
+
+ case Theme::DATA_TYPE_CONSTANT:
+ item_list = &tree_constant_items;
+ break;
+
+ case Theme::DATA_TYPE_FONT:
+ item_list = &tree_font_items;
+ break;
+
+ case Theme::DATA_TYPE_FONT_SIZE:
+ item_list = &tree_font_size_items;
+ break;
+
+ case Theme::DATA_TYPE_ICON:
+ item_list = &tree_icon_items;
+ break;
+
+ case Theme::DATA_TYPE_STYLEBOX:
+ item_list = &tree_stylebox_items;
+ break;
+
+ case Theme::DATA_TYPE_MAX:
+ return; // Can't happen, but silences warning.
+ }
+
+ updating_tree = true;
+
+ for (List<TreeItem *>::Element *E = item_list->front(); E; E = E->next()) {
+ TreeItem *child_item = E->get();
+ if (!child_item) {
+ continue;
+ }
+
+ child_item->set_checked(IMPORT_ITEM, true);
+ child_item->set_checked(IMPORT_ITEM_DATA, true);
+ _update_parent_items(child_item);
+ _store_selected_item(child_item);
+ }
+
+ updating_tree = false;
+}
+
+void ThemeItemImportTree::_deselect_all_data_type_pressed(int p_data_type) {
+ ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds.");
+
+ if (updating_tree) {
+ return;
+ }
+
+ Theme::DataType data_type = (Theme::DataType)p_data_type;
+ List<TreeItem *> *item_list;
+
+ switch (data_type) {
+ case Theme::DATA_TYPE_COLOR:
+ item_list = &tree_color_items;
+ break;
+
+ case Theme::DATA_TYPE_CONSTANT:
+ item_list = &tree_constant_items;
+ break;
+
+ case Theme::DATA_TYPE_FONT:
+ item_list = &tree_font_items;
+ break;
+
+ case Theme::DATA_TYPE_FONT_SIZE:
+ item_list = &tree_font_size_items;
+ break;
+
+ case Theme::DATA_TYPE_ICON:
+ item_list = &tree_icon_items;
+ break;
+
+ case Theme::DATA_TYPE_STYLEBOX:
+ item_list = &tree_stylebox_items;
+ break;
+
+ case Theme::DATA_TYPE_MAX:
+ return; // Can't happen, but silences warning.
+ }
+
+ updating_tree = true;
+
+ for (List<TreeItem *>::Element *E = item_list->front(); E; E = E->next()) {
+ TreeItem *child_item = E->get();
+ if (!child_item) {
+ continue;
+ }
+
+ child_item->set_checked(IMPORT_ITEM, false);
+ child_item->set_checked(IMPORT_ITEM_DATA, false);
+ _update_parent_items(child_item);
+ _store_selected_item(child_item);
+ }
+
+ updating_tree = false;
+}
+
+void ThemeItemImportTree::_import_selected() {
+ if (selected_items.size() == 0) {
+ EditorNode::get_singleton()->show_accept(TTR("Nothing was selected for the import."), TTR("OK"));
+ return;
+ }
+
+ ProgressDialog::get_singleton()->add_task("import_theme_items", TTR("Importing Theme Items"), selected_items.size());
+
+ int idx = 0;
+ for (Map<ThemeItem, ItemCheckedState>::Element *E = selected_items.front(); E; E = E->next()) {
+ // Arbitrary number of items to skip from reporting.
+ // Reduces the number of UI updates that this causes when copying large themes.
+ if (idx % 10 == 0) {
+ Array arr;
+ arr.push_back(idx + 1);
+ arr.push_back(selected_items.size());
+ ProgressDialog::get_singleton()->task_step("import_theme_items", TTR("Importing items {n}/{n}").format(arr, "{n}"), idx);
+ }
+
+ ItemCheckedState cs = E->get();
+ ThemeItem ti = E->key();
+
+ if (cs == SELECT_IMPORT_DEFINITION || cs == SELECT_IMPORT_FULL) {
+ Variant item_value = Variant();
+
+ if (cs == SELECT_IMPORT_FULL) {
+ item_value = base_theme->get_theme_item(ti.data_type, ti.item_name, ti.type_name);
+ } else {
+ switch (ti.data_type) {
+ case Theme::DATA_TYPE_COLOR:
+ item_value = Color();
+ break;
+
+ case Theme::DATA_TYPE_CONSTANT:
+ item_value = 0;
+ break;
+
+ case Theme::DATA_TYPE_FONT:
+ item_value = Ref<Font>();
+ break;
+
+ case Theme::DATA_TYPE_FONT_SIZE:
+ item_value = -1;
+ break;
+
+ case Theme::DATA_TYPE_ICON:
+ item_value = Ref<Texture2D>();
+ break;
+
+ case Theme::DATA_TYPE_STYLEBOX:
+ item_value = Ref<StyleBox>();
+ break;
+
+ case Theme::DATA_TYPE_MAX:
+ break; // Can't happen, but silences warning.
+ }
+ }
+
+ edited_theme->set_theme_item(ti.data_type, ti.item_name, ti.type_name, item_value);
+ }
+
+ idx++;
+ }
+
+ ProgressDialog::get_singleton()->end_task("import_theme_items");
+ emit_signal("items_imported");
+}
+
+void ThemeItemImportTree::set_edited_theme(const Ref<Theme> &p_theme) {
+ edited_theme = p_theme;
+}
+
+void ThemeItemImportTree::set_base_theme(const Ref<Theme> &p_theme) {
+ base_theme = p_theme;
+}
+
+void ThemeItemImportTree::reset_item_tree() {
+ import_items_filter->clear();
+ selected_items.clear();
+
+ total_selected_colors_label->hide();
+ total_selected_constants_label->hide();
+ total_selected_fonts_label->hide();
+ total_selected_font_sizes_label->hide();
+ total_selected_icons_label->hide();
+ total_selected_styleboxes_label->hide();
+
+ _update_items_tree();
+}
+
+bool ThemeItemImportTree::has_selected_items() const {
+ return (selected_items.size() > 0);
+}
+
+void ThemeItemImportTree::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ select_icons_warning_icon->set_texture(get_theme_icon("StatusWarning", "EditorIcons"));
+ select_icons_warning->add_theme_color_override("font_color", get_theme_color("disabled_font_color", "Editor"));
+
+ // Bottom panel buttons.
+ import_collapse_types_button->set_icon(get_theme_icon("CollapseTree", "EditorIcons"));
+ import_expand_types_button->set_icon(get_theme_icon("ExpandTree", "EditorIcons"));
+
+ import_select_all_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons"));
+ import_select_full_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons"));
+ import_deselect_all_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons"));
+
+ // Side panel buttons.
+ select_colors_icon->set_texture(get_theme_icon("Color", "EditorIcons"));
+ deselect_all_colors_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons"));
+ select_all_colors_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons"));
+ select_full_colors_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons"));
+
+ select_constants_icon->set_texture(get_theme_icon("MemberConstant", "EditorIcons"));
+ deselect_all_constants_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons"));
+ select_all_constants_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons"));
+ select_full_constants_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons"));
+
+ select_fonts_icon->set_texture(get_theme_icon("Font", "EditorIcons"));
+ deselect_all_fonts_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons"));
+ select_all_fonts_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons"));
+ select_full_fonts_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons"));
+
+ select_font_sizes_icon->set_texture(get_theme_icon("FontSize", "EditorIcons"));
+ deselect_all_font_sizes_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons"));
+ select_all_font_sizes_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons"));
+ select_full_font_sizes_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons"));
+
+ select_icons_icon->set_texture(get_theme_icon("ImageTexture", "EditorIcons"));
+ deselect_all_icons_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons"));
+ select_all_icons_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons"));
+ select_full_icons_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons"));
+
+ select_styleboxes_icon->set_texture(get_theme_icon("StyleBoxFlat", "EditorIcons"));
+ deselect_all_styleboxes_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons"));
+ select_all_styleboxes_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons"));
+ select_full_styleboxes_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons"));
+ } break;
+ }
+}
+
+void ThemeItemImportTree::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("items_imported"));
+}
+
+ThemeItemImportTree::ThemeItemImportTree() {
+ HBoxContainer *import_items_filter_hb = memnew(HBoxContainer);
+ add_child(import_items_filter_hb);
+ Label *import_items_filter_label = memnew(Label);
+ import_items_filter_label->set_text(TTR("Filter:"));
+ import_items_filter_hb->add_child(import_items_filter_label);
+ import_items_filter = memnew(LineEdit);
+ import_items_filter->set_clear_button_enabled(true);
+ import_items_filter->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ import_items_filter_hb->add_child(import_items_filter);
+ import_items_filter->connect("text_changed", callable_mp(this, &ThemeItemImportTree::_filter_text_changed));
+
+ HBoxContainer *import_main_hb = memnew(HBoxContainer);
+ import_main_hb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ add_child(import_main_hb);
+
+ import_items_tree = memnew(Tree);
+ import_items_tree->set_hide_root(true);
+ import_items_tree->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ import_main_hb->add_child(import_items_tree);
+ import_items_tree->connect("item_edited", callable_mp(this, &ThemeItemImportTree::_tree_item_edited));
+
+ import_items_tree->set_columns(3);
+ import_items_tree->set_column_titles_visible(true);
+ import_items_tree->set_column_title(IMPORT_ITEM, TTR("Import"));
+ import_items_tree->set_column_title(IMPORT_ITEM_DATA, TTR("With Data"));
+ import_items_tree->set_column_expand(0, true);
+ import_items_tree->set_column_expand(IMPORT_ITEM, false);
+ import_items_tree->set_column_expand(IMPORT_ITEM_DATA, false);
+ import_items_tree->set_column_min_width(0, 160 * EDSCALE);
+ import_items_tree->set_column_min_width(IMPORT_ITEM, 80 * EDSCALE);
+ import_items_tree->set_column_min_width(IMPORT_ITEM_DATA, 80 * EDSCALE);
+
+ ScrollContainer *import_bulk_sc = memnew(ScrollContainer);
+ import_bulk_sc->set_custom_minimum_size(Size2(260.0, 0.0) * EDSCALE);
+ import_bulk_sc->set_enable_h_scroll(false);
+ import_main_hb->add_child(import_bulk_sc);
+ VBoxContainer *import_bulk_vb = memnew(VBoxContainer);
+ import_bulk_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ import_bulk_sc->add_child(import_bulk_vb);
+
+ Label *import_bulk_label = memnew(Label);
+ import_bulk_label->set_text(TTR("Select by data type:"));
+ import_bulk_vb->add_child(import_bulk_label);
+
+ select_colors_icon = memnew(TextureRect);
+ select_colors_label = memnew(Label);
+ deselect_all_colors_button = memnew(Button);
+ select_all_colors_button = memnew(Button);
+ select_full_colors_button = memnew(Button);
+ total_selected_colors_label = memnew(Label);
+
+ select_constants_icon = memnew(TextureRect);
+ select_constants_label = memnew(Label);
+ deselect_all_constants_button = memnew(Button);
+ select_all_constants_button = memnew(Button);
+ select_full_constants_button = memnew(Button);
+ total_selected_constants_label = memnew(Label);
+
+ select_fonts_icon = memnew(TextureRect);
+ select_fonts_label = memnew(Label);
+ deselect_all_fonts_button = memnew(Button);
+ select_all_fonts_button = memnew(Button);
+ select_full_fonts_button = memnew(Button);
+ total_selected_fonts_label = memnew(Label);
+
+ select_font_sizes_icon = memnew(TextureRect);
+ select_font_sizes_label = memnew(Label);
+ deselect_all_font_sizes_button = memnew(Button);
+ select_all_font_sizes_button = memnew(Button);
+ select_full_font_sizes_button = memnew(Button);
+ total_selected_font_sizes_label = memnew(Label);
+
+ select_icons_icon = memnew(TextureRect);
+ select_icons_label = memnew(Label);
+ deselect_all_icons_button = memnew(Button);
+ select_all_icons_button = memnew(Button);
+ select_full_icons_button = memnew(Button);
+ total_selected_icons_label = memnew(Label);
+
+ select_styleboxes_icon = memnew(TextureRect);
+ select_styleboxes_label = memnew(Label);
+ deselect_all_styleboxes_button = memnew(Button);
+ select_all_styleboxes_button = memnew(Button);
+ select_full_styleboxes_button = memnew(Button);
+ total_selected_styleboxes_label = memnew(Label);
+
+ for (int i = 0; i < Theme::DATA_TYPE_MAX; i++) {
+ Theme::DataType dt = (Theme::DataType)i;
+
+ TextureRect *select_items_icon;
+ Label *select_items_label;
+ Button *deselect_all_items_button;
+ Button *select_all_items_button;
+ Button *select_full_items_button;
+ Label *total_selected_items_label;
+
+ String items_title = "";
+ String select_all_items_tooltip = "";
+ String select_full_items_tooltip = "";
+ String deselect_all_items_tooltip = "";
+
+ switch (dt) {
+ case Theme::DATA_TYPE_COLOR:
+ select_items_icon = select_colors_icon;
+ select_items_label = select_colors_label;
+ deselect_all_items_button = deselect_all_colors_button;
+ select_all_items_button = select_all_colors_button;
+ select_full_items_button = select_full_colors_button;
+ total_selected_items_label = total_selected_colors_label;
+
+ items_title = TTR("Colors");
+ select_all_items_tooltip = TTR("Select all visible color items.");
+ select_full_items_tooltip = TTR("Select all visible color items and their data.");
+ deselect_all_items_tooltip = TTR("Deselect all visible color items.");
+ break;
+
+ case Theme::DATA_TYPE_CONSTANT:
+ select_items_icon = select_constants_icon;
+ select_items_label = select_constants_label;
+ deselect_all_items_button = deselect_all_constants_button;
+ select_all_items_button = select_all_constants_button;
+ select_full_items_button = select_full_constants_button;
+ total_selected_items_label = total_selected_constants_label;
+
+ items_title = TTR("Constants");
+ select_all_items_tooltip = TTR("Select all visible constant items.");
+ select_full_items_tooltip = TTR("Select all visible constant items and their data.");
+ deselect_all_items_tooltip = TTR("Deselect all visible constant items.");
+ break;
+
+ case Theme::DATA_TYPE_FONT:
+ select_items_icon = select_fonts_icon;
+ select_items_label = select_fonts_label;
+ deselect_all_items_button = deselect_all_fonts_button;
+ select_all_items_button = select_all_fonts_button;
+ select_full_items_button = select_full_fonts_button;
+ total_selected_items_label = total_selected_fonts_label;
+
+ items_title = TTR("Fonts");
+ select_all_items_tooltip = TTR("Select all visible font items.");
+ select_full_items_tooltip = TTR("Select all visible font items and their data.");
+ deselect_all_items_tooltip = TTR("Deselect all visible font items.");
+ break;
+
+ case Theme::DATA_TYPE_FONT_SIZE:
+ select_items_icon = select_font_sizes_icon;
+ select_items_label = select_font_sizes_label;
+ deselect_all_items_button = deselect_all_font_sizes_button;
+ select_all_items_button = select_all_font_sizes_button;
+ select_full_items_button = select_full_font_sizes_button;
+ total_selected_items_label = total_selected_font_sizes_label;
+
+ items_title = TTR("Font sizes");
+ select_all_items_tooltip = TTR("Select all visible font size items.");
+ select_full_items_tooltip = TTR("Select all visible font size items and their data.");
+ deselect_all_items_tooltip = TTR("Deselect all visible font size items.");
+ break;
+
+ case Theme::DATA_TYPE_ICON:
+ select_items_icon = select_icons_icon;
+ select_items_label = select_icons_label;
+ deselect_all_items_button = deselect_all_icons_button;
+ select_all_items_button = select_all_icons_button;
+ select_full_items_button = select_full_icons_button;
+ total_selected_items_label = total_selected_icons_label;
+
+ items_title = TTR("Icons");
+ select_all_items_tooltip = TTR("Select all visible icon items.");
+ select_full_items_tooltip = TTR("Select all visible icon items and their data.");
+ deselect_all_items_tooltip = TTR("Deselect all visible icon items.");
+ break;
+
+ case Theme::DATA_TYPE_STYLEBOX:
+ select_items_icon = select_styleboxes_icon;
+ select_items_label = select_styleboxes_label;
+ deselect_all_items_button = deselect_all_styleboxes_button;
+ select_all_items_button = select_all_styleboxes_button;
+ select_full_items_button = select_full_styleboxes_button;
+ total_selected_items_label = total_selected_styleboxes_label;
+
+ items_title = TTR("Styleboxes");
+ select_all_items_tooltip = TTR("Select all visible stylebox items.");
+ select_full_items_tooltip = TTR("Select all visible stylebox items and their data.");
+ deselect_all_items_tooltip = TTR("Deselect all visible stylebox items.");
+ break;
+
+ case Theme::DATA_TYPE_MAX:
+ continue; // Can't happen, but silences warning.
+ }
+
+ if (i > 0) {
+ import_bulk_vb->add_child(memnew(HSeparator));
+ }
+
+ HBoxContainer *all_set = memnew(HBoxContainer);
+ import_bulk_vb->add_child(all_set);
+
+ HBoxContainer *label_set = memnew(HBoxContainer);
+ label_set->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ all_set->add_child(label_set);
+ select_items_icon->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
+ label_set->add_child(select_items_icon);
+ select_items_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ select_items_label->set_clip_text(true);
+ select_items_label->set_text(items_title);
+ label_set->add_child(select_items_label);
+
+ HBoxContainer *button_set = memnew(HBoxContainer);
+ button_set->set_alignment(BoxContainer::ALIGN_END);
+ all_set->add_child(button_set);
+ select_all_items_button->set_flat(true);
+ select_all_items_button->set_tooltip(select_all_items_tooltip);
+ button_set->add_child(select_all_items_button);
+ select_all_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_all_data_type_pressed), varray(i));
+ select_full_items_button->set_flat(true);
+ select_full_items_button->set_tooltip(select_full_items_tooltip);
+ button_set->add_child(select_full_items_button);
+ select_full_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_full_data_type_pressed), varray(i));
+ deselect_all_items_button->set_flat(true);
+ deselect_all_items_button->set_tooltip(deselect_all_items_tooltip);
+ button_set->add_child(deselect_all_items_button);
+ deselect_all_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_deselect_all_data_type_pressed), varray(i));
+
+ total_selected_items_label->set_align(Label::ALIGN_RIGHT);
+ total_selected_items_label->hide();
+ import_bulk_vb->add_child(total_selected_items_label);
+
+ if (dt == Theme::DATA_TYPE_ICON) {
+ select_icons_warning_hb = memnew(HBoxContainer);
+ import_bulk_vb->add_child(select_icons_warning_hb);
+
+ select_icons_warning_icon = memnew(TextureRect);
+ select_icons_warning_icon->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
+ select_icons_warning_hb->add_child(select_icons_warning_icon);
+
+ select_icons_warning = memnew(Label);
+ select_icons_warning->set_text(TTR("Caution: Adding icon data may considerably increase the size of your Theme resource."));
+ select_icons_warning->set_autowrap(true);
+ select_icons_warning->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ select_icons_warning_hb->add_child(select_icons_warning);
+ }
+ }
+
+ add_child(memnew(HSeparator));
+
+ HBoxContainer *import_buttons = memnew(HBoxContainer);
+ add_child(import_buttons);
+
+ import_collapse_types_button = memnew(Button);
+ import_collapse_types_button->set_flat(true);
+ import_collapse_types_button->set_tooltip(TTR("Collapse types."));
+ import_buttons->add_child(import_collapse_types_button);
+ import_collapse_types_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_toggle_type_items), varray(true));
+ import_expand_types_button = memnew(Button);
+ import_expand_types_button->set_flat(true);
+ import_expand_types_button->set_tooltip(TTR("Expand types."));
+ import_buttons->add_child(import_expand_types_button);
+ import_expand_types_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_toggle_type_items), varray(false));
+
+ import_buttons->add_child(memnew(VSeparator));
+
+ import_select_all_button = memnew(Button);
+ import_select_all_button->set_flat(true);
+ import_select_all_button->set_text(TTR("Select All"));
+ import_select_all_button->set_tooltip(TTR("Select all Theme items."));
+ import_buttons->add_child(import_select_all_button);
+ import_select_all_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_all_items_pressed));
+ import_select_full_button = memnew(Button);
+ import_select_full_button->set_flat(true);
+ import_select_full_button->set_text(TTR("Select With Data"));
+ import_select_full_button->set_tooltip(TTR("Select all Theme items with item data."));
+ import_buttons->add_child(import_select_full_button);
+ import_select_full_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_full_items_pressed));
+ import_deselect_all_button = memnew(Button);
+ import_deselect_all_button->set_flat(true);
+ import_deselect_all_button->set_text(TTR("Deselect All"));
+ import_deselect_all_button->set_tooltip(TTR("Deselect all Theme items."));
+ import_buttons->add_child(import_deselect_all_button);
+ import_deselect_all_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_deselect_all_items_pressed));
+
+ import_buttons->add_spacer();
+
+ Button *import_add_selected_button = memnew(Button);
+ import_add_selected_button->set_text(TTR("Import Selected"));
+ import_buttons->add_child(import_add_selected_button);
+ import_add_selected_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_import_selected));
+}
+
+void ThemeItemEditorDialog::ok_pressed() {
+ if (import_default_theme_items->has_selected_items() || import_editor_theme_items->has_selected_items() || import_other_theme_items->has_selected_items()) {
+ confirm_closing_dialog->set_text(TTR("Import Items tab has some items selected. Selection will be lost upon closing this window.\nClose anyway?"));
+ confirm_closing_dialog->popup_centered(Size2i(380, 120) * EDSCALE);
+ return;
+ }
+
+ hide();
+}
+
+void ThemeItemEditorDialog::_close_dialog() {
+ hide();
+}
+
void ThemeItemEditorDialog::_dialog_about_to_show() {
ERR_FAIL_COND(edited_theme.is_null());
_update_edit_types();
+
+ import_default_theme_items->set_edited_theme(edited_theme);
+ import_default_theme_items->set_base_theme(Theme::get_default());
+ import_default_theme_items->reset_item_tree();
+
+ import_editor_theme_items->set_edited_theme(edited_theme);
+ import_editor_theme_items->set_base_theme(EditorNode::get_singleton()->get_theme_base()->get_theme());
+ import_editor_theme_items->reset_item_tree();
+
+ import_other_theme_items->set_edited_theme(edited_theme);
+ import_other_theme_items->reset_item_tree();
}
void ThemeItemEditorDialog::_update_edit_types() {
@@ -79,11 +1254,6 @@ void ThemeItemEditorDialog::_update_edit_types() {
base_theme->get_type_list(&default_types);
default_types.sort_custom<StringName::AlphCompare>();
- edit_add_class_options->clear();
- for (List<StringName>::Element *E = default_types.front(); E; E = E->next()) {
- edit_add_class_options->add_item(E->get());
- }
-
String selected_type = "";
Vector<int> selected_ids = edit_type_list->get_selected_items();
if (selected_ids.size() > 0) {
@@ -127,7 +1297,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
List<StringName> names;
- {
+ { // Colors.
names.clear();
edited_theme->get_color_list(p_item_type, &names);
@@ -148,7 +1318,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
}
}
- {
+ { // Constants.
names.clear();
edited_theme->get_constant_list(p_item_type, &names);
@@ -169,7 +1339,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
}
}
- {
+ { // Fonts.
names.clear();
edited_theme->get_font_list(p_item_type, &names);
@@ -190,7 +1360,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
}
}
- {
+ { // Font sizes.
names.clear();
edited_theme->get_font_size_list(p_item_type, &names);
@@ -211,7 +1381,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
}
}
- {
+ { // Icons.
names.clear();
edited_theme->get_icon_list(p_item_type, &names);
@@ -232,7 +1402,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
}
}
- {
+ { // Styleboxes.
names.clear();
edited_theme->get_stylebox_list(p_item_type, &names);
@@ -280,64 +1450,13 @@ void ThemeItemEditorDialog::_item_tree_button_pressed(Object *p_item, int p_colu
_update_edit_item_tree(edited_item_type);
}
-void ThemeItemEditorDialog::_add_class_type_items() {
- int selected_idx = edit_add_class_options->get_selected();
- String type_name = edit_add_class_options->get_item_text(selected_idx);
- List<StringName> names;
-
- {
- names.clear();
- Theme::get_default()->get_icon_list(type_name, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- edited_theme->set_icon(E->get(), type_name, Ref<Texture2D>());
- }
- }
- {
- names.clear();
- Theme::get_default()->get_stylebox_list(type_name, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- edited_theme->set_stylebox(E->get(), type_name, Ref<StyleBox>());
- }
- }
- {
- names.clear();
- Theme::get_default()->get_font_list(type_name, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- edited_theme->set_font(E->get(), type_name, Ref<Font>());
- }
- }
- {
- names.clear();
- Theme::get_default()->get_font_size_list(type_name, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- edited_theme->set_font_size(E->get(), type_name, Theme::get_default()->get_font_size(E->get(), type_name));
- }
- }
- {
- names.clear();
- Theme::get_default()->get_color_list(type_name, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- edited_theme->set_color(E->get(), type_name, Theme::get_default()->get_color(E->get(), type_name));
- }
- }
- {
- names.clear();
- Theme::get_default()->get_constant_list(type_name, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- edited_theme->set_constant(E->get(), type_name, Theme::get_default()->get_constant(E->get(), type_name));
- }
- }
-
- _update_edit_types();
-}
-
-void ThemeItemEditorDialog::_add_custom_type() {
- edited_theme->add_icon_type(edit_add_custom_value->get_text());
- edited_theme->add_stylebox_type(edit_add_custom_value->get_text());
- edited_theme->add_font_type(edit_add_custom_value->get_text());
- edited_theme->add_font_size_type(edit_add_custom_value->get_text());
- edited_theme->add_color_type(edit_add_custom_value->get_text());
- edited_theme->add_constant_type(edit_add_custom_value->get_text());
+void ThemeItemEditorDialog::_add_theme_type() {
+ edited_theme->add_icon_type(edit_add_type_value->get_text());
+ edited_theme->add_stylebox_type(edit_add_type_value->get_text());
+ edited_theme->add_font_type(edit_add_type_value->get_text());
+ edited_theme->add_font_size_type(edit_add_type_value->get_text());
+ edited_theme->add_color_type(edit_add_type_value->get_text());
+ edited_theme->add_constant_type(edit_add_type_value->get_text());
_update_edit_types();
}
@@ -536,6 +1655,26 @@ void ThemeItemEditorDialog::_edit_theme_item_gui_input(const Ref<InputEvent> &p_
}
}
+void ThemeItemEditorDialog::_open_select_another_theme() {
+ import_another_theme_dialog->popup_file_dialog();
+}
+
+void ThemeItemEditorDialog::_select_another_theme_cbk(const String &p_path) {
+ Ref<Theme> loaded_theme = ResourceLoader::load(p_path);
+ if (loaded_theme.is_null()) {
+ EditorNode::get_singleton()->show_warning(TTR("Invalid file, not a Theme resource."));
+ return;
+ }
+ if (loaded_theme == edited_theme) {
+ EditorNode::get_singleton()->show_warning(TTR("Invalid file, same as the edited Theme resource."));
+ return;
+ }
+
+ import_another_theme_value->set_text(p_path);
+ import_other_theme_items->set_base_theme(loaded_theme);
+ import_other_theme_items->reset_item_tree();
+}
+
void ThemeItemEditorDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
@@ -553,6 +1692,11 @@ void ThemeItemEditorDialog::_notification(int p_what) {
edit_items_remove_class->set_icon(get_theme_icon("Control", "EditorIcons"));
edit_items_remove_custom->set_icon(get_theme_icon("ThemeRemoveCustomItems", "EditorIcons"));
edit_items_remove_all->set_icon(get_theme_icon("ThemeRemoveAllItems", "EditorIcons"));
+
+ import_another_theme_button->set_icon(get_theme_icon("Folder", "EditorIcons"));
+
+ tc->add_theme_style_override("tab_selected", get_theme_stylebox("tab_selected_odd", "TabContainer"));
+ tc->add_theme_style_override("panel", get_theme_stylebox("panel_odd", "TabContainer"));
} break;
}
}
@@ -562,10 +1706,18 @@ void ThemeItemEditorDialog::set_edited_theme(const Ref<Theme> &p_theme) {
}
ThemeItemEditorDialog::ThemeItemEditorDialog() {
- set_title(TTR("Edit Theme Items"));
+ set_title(TTR("Manage Theme Items"));
+ get_ok_button()->set_text(TTR("Close"));
+ set_hide_on_ok(false); // Closing may require a confirmation in some cases.
+
+ tc = memnew(TabContainer);
+ tc->set_tab_align(TabContainer::TabAlign::ALIGN_LEFT);
+ add_child(tc);
+ // Edit Items tab.
HSplitContainer *edit_dialog_hs = memnew(HSplitContainer);
- add_child(edit_dialog_hs);
+ tc->add_child(edit_dialog_hs);
+ tc->set_tab_title(0, TTR("Edit Items"));
VBoxContainer *edit_dialog_side_vb = memnew(VBoxContainer);
edit_dialog_side_vb->set_custom_minimum_size(Size2(200.0, 0.0) * EDSCALE);
@@ -580,33 +1732,19 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() {
edit_dialog_side_vb->add_child(edit_type_list);
edit_type_list->connect("item_selected", callable_mp(this, &ThemeItemEditorDialog::_edited_type_selected));
- Label *edit_add_class_label = memnew(Label);
- edit_add_class_label->set_text(TTR("Add Type from Class:"));
- edit_dialog_side_vb->add_child(edit_add_class_label);
-
- HBoxContainer *edit_add_class = memnew(HBoxContainer);
- edit_dialog_side_vb->add_child(edit_add_class);
- edit_add_class_options = memnew(OptionButton);
- edit_add_class_options->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- edit_add_class->add_child(edit_add_class_options);
- Button *edit_add_class_button = memnew(Button);
- edit_add_class_button->set_text(TTR("Add"));
- edit_add_class->add_child(edit_add_class_button);
- edit_add_class_button->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_add_class_type_items));
-
- Label *edit_add_custom_label = memnew(Label);
- edit_add_custom_label->set_text(TTR("Add Custom Type:"));
- edit_dialog_side_vb->add_child(edit_add_custom_label);
-
- HBoxContainer *edit_add_custom = memnew(HBoxContainer);
- edit_dialog_side_vb->add_child(edit_add_custom);
- edit_add_custom_value = memnew(LineEdit);
- edit_add_custom_value->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- edit_add_custom->add_child(edit_add_custom_value);
- Button *edit_add_custom_button = memnew(Button);
- edit_add_custom_button->set_text(TTR("Add"));
- edit_add_custom->add_child(edit_add_custom_button);
- edit_add_custom_button->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_add_custom_type));
+ Label *edit_add_type_label = memnew(Label);
+ edit_add_type_label->set_text(TTR("Add Type:"));
+ edit_dialog_side_vb->add_child(edit_add_type_label);
+
+ HBoxContainer *edit_add_type_hb = memnew(HBoxContainer);
+ edit_dialog_side_vb->add_child(edit_add_type_hb);
+ edit_add_type_value = memnew(LineEdit);
+ edit_add_type_value->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ edit_add_type_hb->add_child(edit_add_type_value);
+ Button *edit_add_type_button = memnew(Button);
+ edit_add_type_button->set_text(TTR("Add"));
+ edit_add_type_hb->add_child(edit_add_type_button);
+ edit_add_type_button->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_add_theme_type));
VBoxContainer *edit_items_vb = memnew(VBoxContainer);
edit_items_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -616,7 +1754,7 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() {
edit_items_vb->add_child(edit_items_toolbar);
Label *edit_items_toolbar_add_label = memnew(Label);
- edit_items_toolbar_add_label->set_text(TTR("Add:"));
+ edit_items_toolbar_add_label->set_text(TTR("Add Item:"));
edit_items_toolbar->add_child(edit_items_toolbar_add_label);
edit_items_add_color = memnew(Button);
@@ -664,7 +1802,7 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() {
edit_items_toolbar->add_child(memnew(VSeparator));
Label *edit_items_toolbar_remove_label = memnew(Label);
- edit_items_toolbar_remove_label->set_text(TTR("Remove:"));
+ edit_items_toolbar_remove_label->set_text(TTR("Remove Items:"));
edit_items_toolbar->add_child(edit_items_toolbar_remove_label);
edit_items_remove_class = memnew(Button);
@@ -716,6 +1854,57 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() {
edit_theme_item_vb->add_child(theme_item_name);
theme_item_name->connect("gui_input", callable_mp(this, &ThemeItemEditorDialog::_edit_theme_item_gui_input));
edit_theme_item_dialog->connect("confirmed", callable_mp(this, &ThemeItemEditorDialog::_confirm_edit_theme_item));
+
+ // Import Items tab.
+ TabContainer *import_tc = memnew(TabContainer);
+ tc->add_child(import_tc);
+ tc->set_tab_title(1, TTR("Import Items"));
+
+ import_default_theme_items = memnew(ThemeItemImportTree);
+ import_tc->add_child(import_default_theme_items);
+ import_tc->set_tab_title(0, TTR("Default Theme"));
+ import_default_theme_items->connect("items_imported", callable_mp(this, &ThemeItemEditorDialog::_update_edit_types));
+
+ import_editor_theme_items = memnew(ThemeItemImportTree);
+ import_tc->add_child(import_editor_theme_items);
+ import_tc->set_tab_title(1, TTR("Editor Theme"));
+ import_editor_theme_items->connect("items_imported", callable_mp(this, &ThemeItemEditorDialog::_update_edit_types));
+
+ VBoxContainer *import_another_theme_vb = memnew(VBoxContainer);
+
+ HBoxContainer *import_another_file_hb = memnew(HBoxContainer);
+ import_another_theme_vb->add_child(import_another_file_hb);
+ import_another_theme_value = memnew(LineEdit);
+ import_another_theme_value->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ import_another_theme_value->set_editable(false);
+ import_another_file_hb->add_child(import_another_theme_value);
+ import_another_theme_button = memnew(Button);
+ import_another_file_hb->add_child(import_another_theme_button);
+ import_another_theme_button->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_select_another_theme));
+
+ import_another_theme_dialog = memnew(EditorFileDialog);
+ import_another_theme_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
+ import_another_theme_dialog->set_title(TTR("Select Another Theme Resource:"));
+ List<String> ext;
+ ResourceLoader::get_recognized_extensions_for_type("Theme", &ext);
+ for (List<String>::Element *E = ext.front(); E; E = E->next()) {
+ import_another_theme_dialog->add_filter("*." + E->get() + "; Theme Resource");
+ }
+ import_another_file_hb->add_child(import_another_theme_dialog);
+ import_another_theme_dialog->connect("file_selected", callable_mp(this, &ThemeItemEditorDialog::_select_another_theme_cbk));
+
+ import_other_theme_items = memnew(ThemeItemImportTree);
+ import_other_theme_items->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ import_another_theme_vb->add_child(import_other_theme_items);
+
+ import_tc->add_child(import_another_theme_vb);
+ import_tc->set_tab_title(2, TTR("Another Theme"));
+ import_other_theme_items->connect("items_imported", callable_mp(this, &ThemeItemEditorDialog::_update_edit_types));
+
+ confirm_closing_dialog = memnew(ConfirmationDialog);
+ confirm_closing_dialog->set_autowrap(true);
+ add_child(confirm_closing_dialog);
+ confirm_closing_dialog->connect("confirmed", callable_mp(this, &ThemeItemEditorDialog::_close_dialog));
}
void ThemeEditor::edit(const Ref<Theme> &p_theme) {
@@ -742,299 +1931,8 @@ void ThemeEditor::_refresh_interval() {
_propagate_redraw(main_container);
}
-struct _TECategory {
- template <class T>
- struct RefItem {
- Ref<T> item;
- StringName name;
- bool operator<(const RefItem<T> &p) const { return item->get_instance_id() < p.item->get_instance_id(); }
- };
-
- template <class T>
- struct Item {
- T item;
- String name;
- bool operator<(const Item<T> &p) const { return name < p.name; }
- };
-
- Set<RefItem<StyleBox>> stylebox_items;
- Set<RefItem<Font>> font_items;
- Set<Item<int>> font_size_items;
- Set<RefItem<Texture2D>> icon_items;
-
- Set<Item<Color>> color_items;
- Set<Item<int>> constant_items;
-};
-
-void ThemeEditor::_save_template_cbk(String fname) {
- String filename = file_dialog->get_current_path();
-
- Map<String, _TECategory> categories;
-
- // Fill types.
- List<StringName> type_list;
- Theme::get_default()->get_type_list(&type_list);
- for (List<StringName>::Element *E = type_list.front(); E; E = E->next()) {
- categories.insert(E->get(), _TECategory());
- }
-
- // Fill default theme.
- for (Map<String, _TECategory>::Element *E = categories.front(); E; E = E->next()) {
- _TECategory &tc = E->get();
-
- List<StringName> stylebox_list;
- Theme::get_default()->get_stylebox_list(E->key(), &stylebox_list);
- for (List<StringName>::Element *F = stylebox_list.front(); F; F = F->next()) {
- _TECategory::RefItem<StyleBox> it;
- it.name = F->get();
- it.item = Theme::get_default()->get_stylebox(F->get(), E->key());
- tc.stylebox_items.insert(it);
- }
-
- List<StringName> font_list;
- Theme::get_default()->get_font_list(E->key(), &font_list);
- for (List<StringName>::Element *F = font_list.front(); F; F = F->next()) {
- _TECategory::RefItem<Font> it;
- it.name = F->get();
- it.item = Theme::get_default()->get_font(F->get(), E->key());
- tc.font_items.insert(it);
- }
-
- List<StringName> font_size_list;
- Theme::get_default()->get_font_size_list(E->key(), &font_list);
- for (List<StringName>::Element *F = font_size_list.front(); F; F = F->next()) {
- _TECategory::Item<int> it;
- it.name = F->get();
- it.item = Theme::get_default()->get_font_size(F->get(), E->key());
- tc.font_size_items.insert(it);
- }
-
- List<StringName> icon_list;
- Theme::get_default()->get_icon_list(E->key(), &icon_list);
- for (List<StringName>::Element *F = icon_list.front(); F; F = F->next()) {
- _TECategory::RefItem<Texture2D> it;
- it.name = F->get();
- it.item = Theme::get_default()->get_icon(F->get(), E->key());
- tc.icon_items.insert(it);
- }
-
- List<StringName> color_list;
- Theme::get_default()->get_color_list(E->key(), &color_list);
- for (List<StringName>::Element *F = color_list.front(); F; F = F->next()) {
- _TECategory::Item<Color> it;
- it.name = F->get();
- it.item = Theme::get_default()->get_color(F->get(), E->key());
- tc.color_items.insert(it);
- }
-
- List<StringName> constant_list;
- Theme::get_default()->get_constant_list(E->key(), &constant_list);
- for (List<StringName>::Element *F = constant_list.front(); F; F = F->next()) {
- _TECategory::Item<int> it;
- it.name = F->get();
- it.item = Theme::get_default()->get_constant(F->get(), E->key());
- tc.constant_items.insert(it);
- }
- }
-
- FileAccess *file = FileAccess::open(filename, FileAccess::WRITE);
-
- ERR_FAIL_COND_MSG(!file, "Can't save theme to file '" + filename + "'.");
-
- file->store_line("; ******************* ");
- file->store_line("; Template Theme File ");
- file->store_line("; ******************* ");
- file->store_line("; ");
- file->store_line("; Theme Syntax: ");
- file->store_line("; ------------- ");
- file->store_line("; ");
- file->store_line("; Must be placed in section [theme]");
- file->store_line("; ");
- file->store_line("; Type.item = [value] ");
- file->store_line("; ");
- file->store_line("; [value] examples:");
- file->store_line("; ");
- file->store_line("; Type.item = 6 ; numeric constant. ");
- file->store_line("; Type.item = #FF00FF ; HTML color (magenta).");
- file->store_line("; Type.item = #FF00FF55 ; HTML color (magenta with alpha 0x55).");
- file->store_line("; Type.item = icon(image.png) ; icon in a png file (relative to theme file).");
- file->store_line("; Type.item = font(font.xres) ; font in a resource (relative to theme file).");
- file->store_line("; Type.item = sbox(stylebox.xres) ; stylebox in a resource (relative to theme file).");
- file->store_line("; Type.item = sboxf(2,#FF00FF) ; flat stylebox with margin 2.");
- file->store_line("; Type.item = sboxf(2,#FF00FF,#FFFFFF) ; flat stylebox with margin 2 and border.");
- file->store_line("; Type.item = sboxf(2,#FF00FF,#FFFFFF,#000000) ; flat stylebox with margin 2, light & dark borders.");
- file->store_line("; Type.item = sboxt(base.png,2,2,2,2) ; textured stylebox with 3x3 stretch and stretch margins.");
- file->store_line("; -Additionally, 4 extra integers can be added to sboxf and sboxt to specify custom padding of contents:");
- file->store_line("; Type.item = sboxt(base.png,2,2,2,2,5,4,2,4) ;");
- file->store_line("; -Order for all is always left, top, right, bottom.");
- file->store_line("; ");
- file->store_line("; Special values:");
- file->store_line("; Type.item = default ; use the value in the default theme (must exist there).");
- file->store_line("; Type.item = @somebutton_color ; reference to a library value previously defined.");
- file->store_line("; ");
- file->store_line("; Library Syntax: ");
- file->store_line("; --------------- ");
- file->store_line("; ");
- file->store_line("; Must be placed in section [library], but usage is optional.");
- file->store_line("; ");
- file->store_line("; item = [value] ; same as Theme, but assign to library.");
- file->store_line("; ");
- file->store_line("; examples:");
- file->store_line("; ");
- file->store_line("; [library]");
- file->store_line("; ");
- file->store_line("; default_button_color = #FF00FF");
- file->store_line("; ");
- file->store_line("; [theme]");
- file->store_line("; ");
- file->store_line("; Button.color = @default_button_color ; used reference.");
- file->store_line("; ");
- file->store_line("; ******************* ");
- file->store_line("; ");
- file->store_line("; Template Generated Using: " + String(VERSION_FULL_BUILD));
- file->store_line("; ");
- file->store_line("; ");
- file->store_line("");
- file->store_line("[library]");
- file->store_line("");
- file->store_line("; place library stuff here");
- file->store_line("");
- file->store_line("[theme]");
- file->store_line("");
- file->store_line("");
-
- // Write default theme.
- for (Map<String, _TECategory>::Element *E = categories.front(); E; E = E->next()) {
- _TECategory &tc = E->get();
-
- String underline = "; ";
- for (int i = 0; i < E->key().length(); i++) {
- underline += "*";
- }
-
- file->store_line("");
- file->store_line(underline);
- file->store_line("; " + E->key());
- file->store_line(underline);
-
- if (tc.stylebox_items.size()) {
- file->store_line("\n; StyleBox Items:\n");
- }
-
- for (Set<_TECategory::RefItem<StyleBox>>::Element *F = tc.stylebox_items.front(); F; F = F->next()) {
- file->store_line(E->key() + "." + F->get().name + " = default");
- }
-
- if (tc.font_items.size()) {
- file->store_line("\n; Font Items:\n");
- }
-
- for (Set<_TECategory::RefItem<Font>>::Element *F = tc.font_items.front(); F; F = F->next()) {
- file->store_line(E->key() + "." + F->get().name + " = default");
- }
-
- if (tc.font_size_items.size()) {
- file->store_line("\n; Font Size Items:\n");
- }
-
- for (Set<_TECategory::Item<int>>::Element *F = tc.font_size_items.front(); F; F = F->next()) {
- file->store_line(E->key() + "." + F->get().name + " = default");
- }
-
- if (tc.icon_items.size()) {
- file->store_line("\n; Icon Items:\n");
- }
-
- for (Set<_TECategory::RefItem<Texture2D>>::Element *F = tc.icon_items.front(); F; F = F->next()) {
- file->store_line(E->key() + "." + F->get().name + " = default");
- }
-
- if (tc.color_items.size()) {
- file->store_line("\n; Color Items:\n");
- }
-
- for (Set<_TECategory::Item<Color>>::Element *F = tc.color_items.front(); F; F = F->next()) {
- file->store_line(E->key() + "." + F->get().name + " = default");
- }
-
- if (tc.constant_items.size()) {
- file->store_line("\n; Constant Items:\n");
- }
-
- for (Set<_TECategory::Item<int>>::Element *F = tc.constant_items.front(); F; F = F->next()) {
- file->store_line(E->key() + "." + F->get().name + " = default");
- }
- }
-
- file->close();
- memdelete(file);
-}
-
-void ThemeEditor::_theme_create_menu_cbk(int p_option) {
- bool import = (p_option == POPUP_IMPORT_EDITOR_THEME);
-
- Ref<Theme> base_theme;
-
- if (p_option == POPUP_CREATE_EMPTY) {
- base_theme = Theme::get_default();
- } else {
- base_theme = EditorNode::get_singleton()->get_theme_base()->get_theme();
- }
-
- {
- List<StringName> types;
- base_theme->get_type_list(&types);
-
- for (List<StringName>::Element *T = types.front(); T; T = T->next()) {
- StringName type = T->get();
-
- List<StringName> icons;
- base_theme->get_icon_list(type, &icons);
-
- for (List<StringName>::Element *E = icons.front(); E; E = E->next()) {
- theme->set_icon(E->get(), type, import ? base_theme->get_icon(E->get(), type) : Ref<Texture2D>());
- }
-
- List<StringName> styleboxs;
- base_theme->get_stylebox_list(type, &styleboxs);
-
- for (List<StringName>::Element *E = styleboxs.front(); E; E = E->next()) {
- theme->set_stylebox(E->get(), type, import ? base_theme->get_stylebox(E->get(), type) : Ref<StyleBox>());
- }
-
- List<StringName> fonts;
- base_theme->get_font_list(type, &fonts);
-
- for (List<StringName>::Element *E = fonts.front(); E; E = E->next()) {
- theme->set_font(E->get(), type, Ref<Font>());
- }
-
- List<StringName> font_sizes;
- base_theme->get_font_size_list(type, &font_sizes);
-
- for (List<StringName>::Element *E = font_sizes.front(); E; E = E->next()) {
- theme->set_font_size(E->get(), type, base_theme->get_font_size(E->get(), type));
- }
-
- List<StringName> colors;
- base_theme->get_color_list(type, &colors);
-
- for (List<StringName>::Element *E = colors.front(); E; E = E->next()) {
- theme->set_color(E->get(), type, import ? base_theme->get_color(E->get(), type) : Color());
- }
-
- List<StringName> constants;
- base_theme->get_constant_list(type, &constants);
-
- for (List<StringName>::Element *E = constants.front(); E; E = E->next()) {
- theme->set_constant(E->get(), type, base_theme->get_constant(E->get(), type));
- }
- }
- }
-}
-
void ThemeEditor::_theme_edit_button_cbk() {
- theme_edit_dialog->popup_centered(Size2(800, 640) * EDSCALE);
+ theme_edit_dialog->popup_centered(Size2(850, 760) * EDSCALE);
}
void ThemeEditor::_notification(int p_what) {
@@ -1059,19 +1957,9 @@ ThemeEditor::ThemeEditor() {
top_menu->add_child(memnew(Label(TTR("Preview:"))));
top_menu->add_spacer(false);
- theme_create_menu = memnew(MenuButton);
- theme_create_menu->set_text(TTR("Create Theme..."));
- theme_create_menu->set_tooltip(TTR("Create a new Theme."));
- theme_create_menu->get_popup()->add_item(TTR("Empty Template"), POPUP_CREATE_EMPTY);
- theme_create_menu->get_popup()->add_separator();
- theme_create_menu->get_popup()->add_item(TTR("Empty Editor Template"), POPUP_CREATE_EDITOR_EMPTY);
- theme_create_menu->get_popup()->add_item(TTR("From Current Editor Theme"), POPUP_IMPORT_EDITOR_THEME);
- top_menu->add_child(theme_create_menu);
- theme_create_menu->get_popup()->connect("id_pressed", callable_mp(this, &ThemeEditor::_theme_create_menu_cbk));
-
theme_edit_button = memnew(Button);
- theme_edit_button->set_text(TTR("Edit Theme Items"));
- theme_edit_button->set_tooltip(TTR("Customize Theme items."));
+ theme_edit_button->set_text(TTR("Manage Items"));
+ theme_edit_button->set_tooltip(TTR("Add, remove, organize and import Theme items."));
theme_edit_button->set_flat(true);
theme_edit_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_edit_button_cbk));
top_menu->add_child(theme_edit_button);
@@ -1260,11 +2148,6 @@ ThemeEditor::ThemeEditor() {
theme_edit_dialog = memnew(ThemeItemEditorDialog);
theme_edit_dialog->hide();
add_child(theme_edit_dialog);
-
- file_dialog = memnew(EditorFileDialog);
- file_dialog->add_filter("*.theme ; " + TTR("Theme File"));
- add_child(file_dialog);
- file_dialog->connect("file_selected", callable_mp(this, &ThemeEditor::_save_template_cbk));
}
void ThemeEditorPlugin::edit(Object *p_node) {
diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h
index 0a840aecd7..c42ebf1a19 100644
--- a/editor/plugins/theme_editor_plugin.h
+++ b/editor/plugins/theme_editor_plugin.h
@@ -41,14 +41,150 @@
#include "editor/editor_node.h"
+class ThemeItemImportTree : public VBoxContainer {
+ GDCLASS(ThemeItemImportTree, VBoxContainer);
+
+ Ref<Theme> edited_theme;
+ Ref<Theme> base_theme;
+
+ struct ThemeItem {
+ String type_name;
+ Theme::DataType data_type;
+ String item_name;
+
+ bool operator<(const ThemeItem &p_item) const {
+ if (type_name == p_item.type_name && data_type == p_item.data_type) {
+ return item_name < p_item.item_name;
+ }
+ if (type_name == p_item.type_name) {
+ return data_type < p_item.data_type;
+ }
+ return type_name < p_item.type_name;
+ }
+ };
+
+ enum ItemCheckedState {
+ SELECT_IMPORT_DEFINITION,
+ SELECT_IMPORT_FULL,
+ };
+
+ Map<ThemeItem, ItemCheckedState> selected_items;
+
+ LineEdit *import_items_filter;
+
+ Tree *import_items_tree;
+ List<TreeItem *> tree_color_items;
+ List<TreeItem *> tree_constant_items;
+ List<TreeItem *> tree_font_items;
+ List<TreeItem *> tree_font_size_items;
+ List<TreeItem *> tree_icon_items;
+ List<TreeItem *> tree_stylebox_items;
+
+ bool updating_tree = false;
+
+ enum ItemActionFlag {
+ IMPORT_ITEM = 1,
+ IMPORT_ITEM_DATA = 2,
+ };
+
+ TextureRect *select_colors_icon;
+ Label *select_colors_label;
+ Button *select_all_colors_button;
+ Button *select_full_colors_button;
+ Button *deselect_all_colors_button;
+ Label *total_selected_colors_label;
+
+ TextureRect *select_constants_icon;
+ Label *select_constants_label;
+ Button *select_all_constants_button;
+ Button *select_full_constants_button;
+ Button *deselect_all_constants_button;
+ Label *total_selected_constants_label;
+
+ TextureRect *select_fonts_icon;
+ Label *select_fonts_label;
+ Button *select_all_fonts_button;
+ Button *select_full_fonts_button;
+ Button *deselect_all_fonts_button;
+ Label *total_selected_fonts_label;
+
+ TextureRect *select_font_sizes_icon;
+ Label *select_font_sizes_label;
+ Button *select_all_font_sizes_button;
+ Button *select_full_font_sizes_button;
+ Button *deselect_all_font_sizes_button;
+ Label *total_selected_font_sizes_label;
+
+ TextureRect *select_icons_icon;
+ Label *select_icons_label;
+ Button *select_all_icons_button;
+ Button *select_full_icons_button;
+ Button *deselect_all_icons_button;
+ Label *total_selected_icons_label;
+
+ TextureRect *select_styleboxes_icon;
+ Label *select_styleboxes_label;
+ Button *select_all_styleboxes_button;
+ Button *select_full_styleboxes_button;
+ Button *deselect_all_styleboxes_button;
+ Label *total_selected_styleboxes_label;
+
+ HBoxContainer *select_icons_warning_hb;
+ TextureRect *select_icons_warning_icon;
+ Label *select_icons_warning;
+
+ Button *import_collapse_types_button;
+ Button *import_expand_types_button;
+ Button *import_select_all_button;
+ Button *import_select_full_button;
+ Button *import_deselect_all_button;
+
+ void _update_items_tree();
+ void _toggle_type_items(bool p_collapse);
+ void _filter_text_changed(const String &p_value);
+
+ void _store_selected_item(TreeItem *p_tree_item);
+ void _restore_selected_item(TreeItem *p_tree_item);
+ void _update_total_selected(Theme::DataType p_data_type);
+
+ void _tree_item_edited();
+ void _select_all_subitems(TreeItem *p_root_item, bool p_select_with_data);
+ void _deselect_all_subitems(TreeItem *p_root_item, bool p_deselect_completely);
+ void _update_parent_items(TreeItem *p_root_item);
+
+ void _select_all_items_pressed();
+ void _select_full_items_pressed();
+ void _deselect_all_items_pressed();
+
+ void _select_all_data_type_pressed(int p_data_type);
+ void _select_full_data_type_pressed(int p_data_type);
+ void _deselect_all_data_type_pressed(int p_data_type);
+
+ void _import_selected();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void set_edited_theme(const Ref<Theme> &p_theme);
+ void set_base_theme(const Ref<Theme> &p_theme);
+ void reset_item_tree();
+
+ bool has_selected_items() const;
+
+ ThemeItemImportTree();
+};
+
class ThemeItemEditorDialog : public AcceptDialog {
GDCLASS(ThemeItemEditorDialog, AcceptDialog);
Ref<Theme> edited_theme;
+ TabContainer *tc;
+
ItemList *edit_type_list;
- OptionButton *edit_add_class_options;
- LineEdit *edit_add_custom_value;
+ LineEdit *edit_add_type_value;
String edited_item_type;
Button *edit_items_add_color;
@@ -83,6 +219,19 @@ class ThemeItemEditorDialog : public AcceptDialog {
String edit_item_old_name;
Theme::DataType edit_item_data_type = Theme::DATA_TYPE_MAX;
+ ThemeItemImportTree *import_default_theme_items;
+ ThemeItemImportTree *import_editor_theme_items;
+ ThemeItemImportTree *import_other_theme_items;
+
+ LineEdit *import_another_theme_value;
+ Button *import_another_theme_button;
+ EditorFileDialog *import_another_theme_dialog;
+
+ ConfirmationDialog *confirm_closing_dialog;
+
+ void ok_pressed() override;
+ void _close_dialog();
+
void _dialog_about_to_show();
void _update_edit_types();
void _edited_type_selected(int p_item_idx);
@@ -90,8 +239,7 @@ class ThemeItemEditorDialog : public AcceptDialog {
void _update_edit_item_tree(String p_item_type);
void _item_tree_button_pressed(Object *p_item, int p_column, int p_id);
- void _add_class_type_items();
- void _add_custom_type();
+ void _add_theme_type();
void _add_theme_item(Theme::DataType p_data_type, String p_item_name, String p_item_type);
void _remove_data_type_items(Theme::DataType p_data_type, String p_item_type);
void _remove_class_items();
@@ -103,6 +251,9 @@ class ThemeItemEditorDialog : public AcceptDialog {
void _confirm_edit_theme_item();
void _edit_theme_item_gui_input(const Ref<InputEvent> &p_event);
+ void _open_select_another_theme();
+ void _select_another_theme_cbk(const String &p_path);
+
protected:
void _notification(int p_what);
@@ -115,29 +266,18 @@ public:
class ThemeEditor : public VBoxContainer {
GDCLASS(ThemeEditor, VBoxContainer);
- Panel *main_panel;
- MarginContainer *main_container;
Ref<Theme> theme;
- EditorFileDialog *file_dialog;
-
double time_left = 0;
Button *theme_edit_button;
- MenuButton *theme_create_menu;
ThemeItemEditorDialog *theme_edit_dialog;
- enum CreatePopupMode {
- POPUP_CREATE_EMPTY,
- POPUP_CREATE_EDITOR_EMPTY,
- POPUP_IMPORT_EDITOR_THEME,
- };
-
+ Panel *main_panel;
+ MarginContainer *main_container;
Tree *test_tree;
- void _save_template_cbk(String fname);
void _theme_edit_button_cbk();
- void _theme_create_menu_cbk(int p_option);
void _propagate_redraw(Control *p_at);
void _refresh_interval();
diff --git a/editor/plugins/tile_map_editor_plugin.cpp b/editor/plugins/tile_map_editor_plugin.cpp
deleted file mode 100644
index 1d6ff92e0c..0000000000
--- a/editor/plugins/tile_map_editor_plugin.cpp
+++ /dev/null
@@ -1,2335 +0,0 @@
-/*************************************************************************/
-/* tile_map_editor_plugin.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_map_editor_plugin.h"
-
-#include "canvas_item_editor_plugin.h"
-#include "core/input/input.h"
-#include "core/math/math_funcs.h"
-#include "core/os/keyboard.h"
-#include "editor/editor_scale.h"
-#include "editor/editor_settings.h"
-#include "scene/gui/split_container.h"
-
-void TileMapEditor::_node_removed(Node *p_node) {
- if (p_node == node && node) {
- Callable callable_tileset_settings_changed = callable_mp(this, &TileMapEditor::_tileset_settings_changed);
-
- if (node->is_connected("settings_changed", callable_tileset_settings_changed)) {
- // Fixes #44824, which describes a situation where you can reselect a TileMap node without first de-selecting it when switching scenes.
- node->disconnect("settings_changed", callable_tileset_settings_changed);
- }
- node = nullptr;
- }
-}
-
-void TileMapEditor::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_PROCESS: {
- if (bucket_queue.size()) {
- CanvasItemEditor::get_singleton()->update_viewport();
- }
-
- } break;
-
- case NOTIFICATION_ENTER_TREE: {
- get_tree()->connect("node_removed", callable_mp(this, &TileMapEditor::_node_removed));
- [[fallthrough]];
- }
-
- case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
- if (is_visible_in_tree()) {
- _update_palette();
- }
-
- paint_button->set_icon(get_theme_icon("Edit", "EditorIcons"));
- line_button->set_icon(get_theme_icon("CurveLinear", "EditorIcons"));
- rectangle_button->set_icon(get_theme_icon("Rectangle", "EditorIcons"));
- bucket_fill_button->set_icon(get_theme_icon("Bucket", "EditorIcons"));
- picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons"));
- select_button->set_icon(get_theme_icon("ActionCopy", "EditorIcons"));
-
- rotate_left_button->set_icon(get_theme_icon("RotateLeft", "EditorIcons"));
- rotate_right_button->set_icon(get_theme_icon("RotateRight", "EditorIcons"));
- flip_horizontal_button->set_icon(get_theme_icon("MirrorX", "EditorIcons"));
- flip_vertical_button->set_icon(get_theme_icon("MirrorY", "EditorIcons"));
- clear_transform_button->set_icon(get_theme_icon("Clear", "EditorIcons"));
-
- search_box->set_right_icon(get_theme_icon("Search", "EditorIcons"));
- search_box->set_clear_button_enabled(true);
-
- PopupMenu *p = options->get_popup();
- p->set_item_icon(p->get_item_index(OPTION_CUT), get_theme_icon("ActionCut", "EditorIcons"));
- p->set_item_icon(p->get_item_index(OPTION_COPY), get_theme_icon("Duplicate", "EditorIcons"));
- p->set_item_icon(p->get_item_index(OPTION_ERASE_SELECTION), get_theme_icon("Remove", "EditorIcons"));
-
- } break;
-
- case NOTIFICATION_EXIT_TREE: {
- get_tree()->disconnect("node_removed", callable_mp(this, &TileMapEditor::_node_removed));
- } break;
-
- case NOTIFICATION_APPLICATION_FOCUS_OUT: {
- if (tool == TOOL_PAINTING) {
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) {
- _set_cell(over_tile, ids, flip_h, flip_v, transpose);
- _finish_undo();
-
- paint_undo.clear();
- }
-
- tool = TOOL_NONE;
- _update_button_tool();
- }
-
- // set flag to ignore over_tile on refocus
- refocus_over_tile = true;
- } break;
- }
-}
-
-void TileMapEditor::_update_button_tool() {
- Button *tb[6] = { paint_button, line_button, rectangle_button, bucket_fill_button, picker_button, select_button };
-
- // Unpress all buttons
- for (int i = 0; i < 6; i++) {
- tb[i]->set_pressed(false);
- }
-
- // Press the good button
- switch (tool) {
- case TOOL_NONE:
- case TOOL_PAINTING: {
- paint_button->set_pressed(true);
- } break;
- case TOOL_LINE_PAINT: {
- line_button->set_pressed(true);
- } break;
- case TOOL_RECTANGLE_PAINT: {
- rectangle_button->set_pressed(true);
- } break;
- case TOOL_BUCKET: {
- bucket_fill_button->set_pressed(true);
- } break;
- case TOOL_PICKING: {
- picker_button->set_pressed(true);
- } break;
- case TOOL_SELECTING: {
- select_button->set_pressed(true);
- } break;
- default:
- break;
- }
-
- if (tool != TOOL_PICKING) {
- last_tool = tool;
- }
-}
-
-void TileMapEditor::_button_tool_select(int p_tool) {
- tool = (Tool)p_tool;
- _update_button_tool();
- switch (tool) {
- case TOOL_SELECTING: {
- selection_active = false;
- } break;
- default:
- break;
- }
- CanvasItemEditor::get_singleton()->update_viewport();
-}
-
-void TileMapEditor::_menu_option(int p_option) {
- switch (p_option) {
- case OPTION_COPY: {
- _update_copydata();
-
- if (selection_active) {
- tool = TOOL_PASTING;
-
- CanvasItemEditor::get_singleton()->update_viewport();
- }
- } break;
- case OPTION_ERASE_SELECTION: {
- if (!selection_active) {
- return;
- }
-
- _start_undo(TTR("Erase Selection"));
- _erase_selection();
- _finish_undo();
-
- selection_active = false;
- copydata.clear();
-
- CanvasItemEditor::get_singleton()->update_viewport();
- } break;
- case OPTION_FIX_INVALID: {
- undo_redo->create_action(TTR("Fix Invalid Tiles"));
- undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
- node->fix_invalid_tiles();
- undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
- undo_redo->commit_action();
-
- } break;
- case OPTION_CUT: {
- if (selection_active) {
- _update_copydata();
-
- _start_undo(TTR("Cut Selection"));
- _erase_selection();
- _finish_undo();
-
- selection_active = false;
-
- tool = TOOL_PASTING;
-
- CanvasItemEditor::get_singleton()->update_viewport();
- }
- } break;
- }
- _update_button_tool();
-}
-
-void TileMapEditor::_palette_selected(int index) {
- _update_palette();
-}
-
-void TileMapEditor::_palette_multi_selected(int index, bool selected) {
- _update_palette();
-}
-
-void TileMapEditor::_palette_input(const Ref<InputEvent> &p_event) {
- const Ref<InputEventMouseButton> mb = p_event;
-
- // Zoom in/out using Ctrl + mouse wheel.
- if (mb.is_valid() && mb->is_pressed() && mb->get_command()) {
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP) {
- size_slider->set_value(size_slider->get_value() + 0.2);
- }
-
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) {
- size_slider->set_value(size_slider->get_value() - 0.2);
- }
- }
-}
-
-void TileMapEditor::_canvas_mouse_enter() {
- mouse_over = true;
- CanvasItemEditor::get_singleton()->update_viewport();
-}
-
-void TileMapEditor::_canvas_mouse_exit() {
- mouse_over = false;
- CanvasItemEditor::get_singleton()->update_viewport();
-}
-
-Vector<int> TileMapEditor::get_selected_tiles() const {
- Vector<int> items = palette->get_selected_items();
-
- if (items.size() == 0) {
- items.push_back(TileMap::INVALID_CELL);
- return items;
- }
-
- for (int i = items.size() - 1; i >= 0; i--) {
- items.write[i] = palette->get_item_metadata(items[i]);
- }
- return items;
-}
-
-void TileMapEditor::set_selected_tiles(Vector<int> p_tiles) {
- palette->deselect_all();
-
- for (int i = p_tiles.size() - 1; i >= 0; i--) {
- int idx = palette->find_metadata(p_tiles[i]);
-
- if (idx >= 0) {
- palette->select(idx, false);
- }
- }
-
- palette->ensure_current_is_visible();
-}
-
-Dictionary TileMapEditor::_create_cell_dictionary(int tile, bool flip_x, bool flip_y, bool transpose, Vector2 autotile_coord) {
- Dictionary cell;
-
- cell["id"] = tile;
- cell["flip_h"] = flip_x;
- cell["flip_y"] = flip_y;
- cell["transpose"] = transpose;
- cell["auto_coord"] = autotile_coord;
-
- return cell;
-}
-
-void TileMapEditor::_create_set_cell_undo_redo(const Vector2 &p_vec, const CellOp &p_cell_old, const CellOp &p_cell_new) {
- Dictionary cell_old = _create_cell_dictionary(p_cell_old.idx, p_cell_old.xf, p_cell_old.yf, p_cell_old.tr, p_cell_old.ac);
- Dictionary cell_new = _create_cell_dictionary(p_cell_new.idx, p_cell_new.xf, p_cell_new.yf, p_cell_new.tr, p_cell_new.ac);
-
- undo_redo->add_undo_method(node, "_set_celld", p_vec, cell_old);
- undo_redo->add_do_method(node, "_set_celld", p_vec, cell_new);
-}
-
-void TileMapEditor::_start_undo(const String &p_action) {
- undo_data.clear();
- undo_redo->create_action(p_action);
-}
-
-void TileMapEditor::_finish_undo() {
- if (undo_data.size()) {
- for (Map<Point2i, CellOp>::Element *E = undo_data.front(); E; E = E->next()) {
- _create_set_cell_undo_redo(E->key(), E->get(), _get_op_from_cell(E->key()));
- }
-
- undo_data.clear();
- }
-
- undo_redo->commit_action();
-}
-
-void TileMapEditor::_set_cell(const Point2i &p_pos, Vector<int> p_values, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord) {
- ERR_FAIL_COND(!node);
-
- if (p_values.size() == 0) {
- return;
- }
-
- int p_value = p_values[Math::rand() % p_values.size()];
- int prev_val = node->get_cell(p_pos.x, p_pos.y);
-
- bool prev_flip_h = node->is_cell_x_flipped(p_pos.x, p_pos.y);
- bool prev_flip_v = node->is_cell_y_flipped(p_pos.x, p_pos.y);
- bool prev_transpose = node->is_cell_transposed(p_pos.x, p_pos.y);
- Vector2 prev_position = node->get_cell_autotile_coord(p_pos.x, p_pos.y);
-
- Vector2 position;
- int current = manual_palette->get_current();
- if (current != -1) {
- if (tool != TOOL_PASTING) {
- position = manual_palette->get_item_metadata(current);
- } else {
- position = p_autotile_coord;
- }
- } else {
- // If there is no manual tile selected, that either means that
- // autotiling is enabled, or the given tile is not autotiling. Either
- // way, the coordinate of the tile does not matter, so assigning it to
- // the coordinate of the existing tile works fine.
- position = prev_position;
- }
-
- if (p_value == prev_val && p_flip_h == prev_flip_h && p_flip_v == prev_flip_v && p_transpose == prev_transpose && prev_position == position) {
- return; // Check that it's actually different.
- }
-
- for (int y = p_pos.y - 1; y <= p_pos.y + 1; y++) {
- for (int x = p_pos.x - 1; x <= p_pos.x + 1; x++) {
- Point2i p = Point2i(x, y);
- if (!undo_data.has(p)) {
- undo_data[p] = _get_op_from_cell(p);
- }
- }
- }
-
- node->_set_celld(p_pos, _create_cell_dictionary(p_value, p_flip_h, p_flip_v, p_transpose, p_autotile_coord));
-
- if (tool == TOOL_PASTING) {
- return;
- }
-
- if (manual_autotile || (p_value != -1 && node->get_tileset()->tile_get_tile_mode(p_value) == TileSet::ATLAS_TILE)) {
- if (current != -1) {
- node->set_cell_autotile_coord(p_pos.x, p_pos.y, position);
- } else if (node->get_tileset()->tile_get_tile_mode(p_value) == TileSet::ATLAS_TILE && priority_atlastile) {
- // BIND_CENTER is used to indicate that bitmask should not update for this tile cell.
- node->get_tileset()->autotile_set_bitmask(p_value, Vector2(p_pos.x, p_pos.y), TileSet::BIND_CENTER);
- node->update_cell_bitmask(p_pos.x, p_pos.y);
- }
- } else {
- node->update_bitmask_area(Point2(p_pos));
- }
-}
-
-void TileMapEditor::_manual_toggled(bool p_enabled) {
- manual_autotile = p_enabled;
- _update_palette();
-}
-
-void TileMapEditor::_priority_toggled(bool p_enabled) {
- priority_atlastile = p_enabled;
- _update_palette();
-}
-
-void TileMapEditor::_text_entered(const String &p_text) {
- canvas_item_editor_viewport->grab_focus();
-}
-
-void TileMapEditor::_text_changed(const String &p_text) {
- _update_palette();
-}
-
-void TileMapEditor::_sbox_input(const Ref<InputEvent> &p_ie) {
- Ref<InputEventKey> k = p_ie;
-
- if (k.is_valid() && (k->get_keycode() == KEY_UP ||
- k->get_keycode() == KEY_DOWN ||
- k->get_keycode() == KEY_PAGEUP ||
- k->get_keycode() == KEY_PAGEDOWN)) {
- palette->call("_gui_input", k);
- search_box->accept_event();
- }
-}
-
-// Implementation detail of TileMapEditor::_update_palette();
-// In modern C++ this could have been inside its body.
-namespace {
-struct _PaletteEntry {
- int id = 0;
- String name;
-
- bool operator<(const _PaletteEntry &p_rhs) const {
- // Natural no case comparison will compare strings based on CharType
- // order (except digits) and on numbers that start on the same position.
- return name.naturalnocasecmp_to(p_rhs.name) < 0;
- }
-};
-} // namespace
-
-void TileMapEditor::_update_palette() {
- if (!node) {
- return;
- }
-
- // Update the clear button.
- clear_transform_button->set_disabled(!flip_h && !flip_v && !transpose);
-
- // Update the palette.
- Vector<int> selected = get_selected_tiles();
- int selected_single = palette->get_current();
- int selected_manual = manual_palette->get_current();
- palette->clear();
- manual_palette->clear();
- manual_palette->hide();
-
- Ref<TileSet> tileset = node->get_tileset();
- if (tileset.is_null()) {
- search_box->set_text("");
- search_box->set_editable(false);
- info_message->show();
- return;
- }
-
- search_box->set_editable(true);
- info_message->hide();
-
- List<int> tiles;
- tileset->get_tile_list(&tiles);
- if (tiles.is_empty()) {
- return;
- }
-
- float min_size = EDITOR_DEF("editors/tile_map/preview_size", 64);
- min_size *= EDSCALE;
- int hseparation = EDITOR_DEF("editors/tile_map/palette_item_hseparation", 8);
- bool show_tile_names = bool(EDITOR_DEF("editors/tile_map/show_tile_names", true));
- bool show_tile_ids = bool(EDITOR_DEF("editors/tile_map/show_tile_ids", false));
- bool sort_by_name = bool(EDITOR_DEF("editors/tile_map/sort_tiles_by_name", true));
-
- palette->add_theme_constant_override("hseparation", hseparation * EDSCALE);
-
- palette->set_fixed_icon_size(Size2(min_size, min_size));
- palette->set_fixed_column_width(min_size * MAX(size_slider->get_value(), 1));
- palette->set_same_column_width(true);
- manual_palette->set_fixed_icon_size(Size2(min_size, min_size));
- manual_palette->set_same_column_width(true);
-
- String filter = search_box->get_text().strip_edges();
-
- Vector<_PaletteEntry> entries;
-
- for (List<int>::Element *E = tiles.front(); E; E = E->next()) {
- String name = tileset->tile_get_name(E->get());
-
- if (name != "") {
- if (show_tile_ids) {
- if (sort_by_name) {
- name = name + " - " + itos(E->get());
- } else {
- name = itos(E->get()) + " - " + name;
- }
- }
- } else {
- name = "#" + itos(E->get());
- }
-
- if (filter != "" && !filter.is_subsequence_ofi(name)) {
- continue;
- }
-
- const _PaletteEntry entry = { E->get(), name };
- entries.push_back(entry);
- }
-
- if (sort_by_name) {
- entries.sort();
- }
-
- for (int i = 0; i < entries.size(); i++) {
- if (show_tile_names) {
- palette->add_item(entries[i].name);
- } else {
- palette->add_item(String());
- }
-
- Ref<Texture2D> tex = tileset->tile_get_texture(entries[i].id);
-
- if (tex.is_valid()) {
- Rect2 region = tileset->tile_get_region(entries[i].id);
-
- if (tileset->tile_get_tile_mode(entries[i].id) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(entries[i].id) == TileSet::ATLAS_TILE) {
- int spacing = tileset->autotile_get_spacing(entries[i].id);
- region.size = tileset->autotile_get_size(entries[i].id);
- region.position += (region.size + Vector2(spacing, spacing)) * tileset->autotile_get_icon_coordinate(entries[i].id);
- }
-
- // Transpose and flip.
- palette->set_item_icon_transposed(palette->get_item_count() - 1, transpose);
- if (flip_h) {
- region.size.x = -region.size.x;
- }
- if (flip_v) {
- region.size.y = -region.size.y;
- }
-
- // Set region.
- if (region.size != Size2()) {
- palette->set_item_icon_region(palette->get_item_count() - 1, region);
- }
-
- // Set icon.
- palette->set_item_icon(palette->get_item_count() - 1, tex);
-
- // Modulation.
- Color color = tileset->tile_get_modulate(entries[i].id);
- palette->set_item_icon_modulate(palette->get_item_count() - 1, color);
- }
-
- palette->set_item_metadata(palette->get_item_count() - 1, entries[i].id);
- }
-
- int sel_tile = selected.get(0);
- if (selected.get(0) != TileMap::INVALID_CELL) {
- set_selected_tiles(selected);
- sel_tile = selected.get(Math::rand() % selected.size());
- } else if (palette->get_item_count() > 0) {
- palette->select(0);
- sel_tile = palette->get_selected_items().get(0);
- }
-
- if (sel_tile != TileMap::INVALID_CELL && ((manual_autotile && tileset->tile_get_tile_mode(sel_tile) == TileSet::AUTO_TILE) || (!priority_atlastile && tileset->tile_get_tile_mode(sel_tile) == TileSet::ATLAS_TILE))) {
- const Map<Vector2, uint32_t> &tiles2 = tileset->autotile_get_bitmask_map(sel_tile);
-
- Vector<Vector2> entries2;
- for (const Map<Vector2, uint32_t>::Element *E = tiles2.front(); E; E = E->next()) {
- entries2.push_back(E->key());
- }
- // Sort tiles in row-major order.
- struct SwapComparator {
- _FORCE_INLINE_ bool operator()(const Vector2 &v_l, const Vector2 &v_r) const {
- return v_l.y != v_r.y ? v_l.y < v_r.y : v_l.x < v_r.x;
- }
- };
- entries2.sort_custom<SwapComparator>();
-
- Ref<Texture2D> tex = tileset->tile_get_texture(sel_tile);
- Color modulate = tileset->tile_get_modulate(sel_tile);
-
- for (int i = 0; i < entries2.size(); i++) {
- manual_palette->add_item(String());
-
- if (tex.is_valid()) {
- Rect2 region = tileset->tile_get_region(sel_tile);
- int spacing = tileset->autotile_get_spacing(sel_tile);
- region.size = tileset->autotile_get_size(sel_tile); // !!
- region.position += (region.size + Vector2(spacing, spacing)) * entries2[i];
-
- if (!region.has_no_area()) {
- manual_palette->set_item_icon_region(manual_palette->get_item_count() - 1, region);
- }
-
- manual_palette->set_item_icon(manual_palette->get_item_count() - 1, tex);
- manual_palette->set_item_icon_modulate(manual_palette->get_item_count() - 1, modulate);
- }
-
- manual_palette->set_item_metadata(manual_palette->get_item_count() - 1, entries2[i]);
- }
- }
-
- if (manual_palette->get_item_count() > 0) {
- // Only show the manual palette if at least tile exists in it.
- if (selected_manual == -1 || selected_single != palette->get_current()) {
- selected_manual = 0;
- }
- if (selected_manual < manual_palette->get_item_count()) {
- manual_palette->set_current(selected_manual);
- }
- manual_palette->show();
- }
-
- if (sel_tile != TileMap::INVALID_CELL && tileset->tile_get_tile_mode(sel_tile) == TileSet::AUTO_TILE) {
- manual_button->show();
- priority_button->hide();
- } else {
- manual_button->hide();
- priority_button->show();
- }
-}
-
-void TileMapEditor::_pick_tile(const Point2 &p_pos) {
- int id = node->get_cell(p_pos.x, p_pos.y);
-
- if (id == TileMap::INVALID_CELL) {
- return;
- }
-
- if (search_box->get_text() != "") {
- search_box->set_text("");
- _update_palette();
- }
-
- flip_h = node->is_cell_x_flipped(p_pos.x, p_pos.y);
- flip_v = node->is_cell_y_flipped(p_pos.x, p_pos.y);
- transpose = node->is_cell_transposed(p_pos.x, p_pos.y);
- autotile_coord = node->get_cell_autotile_coord(p_pos.x, p_pos.y);
-
- Vector<int> selected;
- selected.push_back(id);
- set_selected_tiles(selected);
- _update_palette();
-
- if ((manual_autotile && node->get_tileset()->tile_get_tile_mode(id) == TileSet::AUTO_TILE) || (!priority_atlastile && node->get_tileset()->tile_get_tile_mode(id) == TileSet::ATLAS_TILE)) {
- manual_palette->select(manual_palette->find_metadata((Point2)autotile_coord));
- }
-
- CanvasItemEditor::get_singleton()->update_viewport();
-}
-
-Vector<Vector2> TileMapEditor::_bucket_fill(const Point2i &p_start, bool erase, bool preview) {
- int prev_id = node->get_cell(p_start.x, p_start.y);
- Vector<int> ids;
- ids.push_back(TileMap::INVALID_CELL);
- if (!erase) {
- ids = get_selected_tiles();
-
- if (ids.size() == 0 || ids[0] == TileMap::INVALID_CELL) {
- return Vector<Vector2>();
- }
- } else if (prev_id == TileMap::INVALID_CELL) {
- return Vector<Vector2>();
- }
-
- // Check if the tile variation is the same
- if (ids.size() == 1 && ids[0] == prev_id) {
- int current = manual_palette->get_current();
- if (current == -1) {
- // Same ID, no variation selected, nothing to change
- return Vector<Vector2>();
- }
- Vector2 prev_autotile_coord = node->get_cell_autotile_coord(p_start.x, p_start.y);
- Vector2 autotile_coord = manual_palette->get_item_metadata(current);
- if (autotile_coord == prev_autotile_coord) {
- // Same ID and variation, nothing to change
- return Vector<Vector2>();
- }
- }
-
- Rect2i r = node->get_used_rect();
-
- int area = r.get_area();
- if (preview) {
- // Test if we can re-use the result from preview bucket fill
- bool invalidate_cache = false;
- // Area changed
- if (r != bucket_cache_rect) {
- _clear_bucket_cache();
- }
- // Cache grid is not initialized
- if (bucket_cache_visited == nullptr) {
- bucket_cache_visited = new bool[area];
- invalidate_cache = true;
- }
- // Tile ID changed or position wasn't visited by the previous fill
- const int loc = (p_start.x - r.position.x) + (p_start.y - r.position.y) * r.get_size().x;
- const bool in_range = 0 <= loc && loc < area;
- if (prev_id != bucket_cache_tile || (in_range && !bucket_cache_visited[loc])) {
- invalidate_cache = true;
- }
- if (invalidate_cache) {
- for (int i = 0; i < area; ++i) {
- bucket_cache_visited[i] = false;
- }
- bucket_cache = Vector<Vector2>();
- bucket_cache_tile = prev_id;
- bucket_cache_rect = r;
- bucket_queue.clear();
- }
- }
-
- Vector<Vector2> points;
- Vector<Vector2> non_preview_cache;
- int count = 0;
- int limit = 0;
-
- if (preview) {
- limit = 1024;
- } else {
- bucket_queue.clear();
- }
-
- bucket_queue.push_back(p_start);
-
- while (bucket_queue.size()) {
- Point2i n = bucket_queue.front()->get();
- bucket_queue.pop_front();
-
- if (!r.has_point(n)) {
- continue;
- }
-
- if (node->get_cell(n.x, n.y) == prev_id) {
- if (preview) {
- int loc = (n.x - r.position.x) + (n.y - r.position.y) * r.get_size().x;
- if (bucket_cache_visited[loc]) {
- continue;
- }
- bucket_cache_visited[loc] = true;
- bucket_cache.push_back(n);
- } else {
- if (non_preview_cache.find(n) >= 0) {
- continue;
- }
- points.push_back(n);
- non_preview_cache.push_back(n);
- }
-
- bucket_queue.push_back(Point2i(n.x, n.y + 1));
- bucket_queue.push_back(Point2i(n.x, n.y - 1));
- bucket_queue.push_back(Point2i(n.x + 1, n.y));
- bucket_queue.push_back(Point2i(n.x - 1, n.y));
- count++;
- }
-
- if (limit > 0 && count >= limit) {
- break;
- }
- }
-
- return preview ? bucket_cache : points;
-}
-
-void TileMapEditor::_fill_points(const Vector<Vector2> &p_points, const Dictionary &p_op) {
- int len = p_points.size();
- const Vector2 *pr = p_points.ptr();
-
- Vector<int> ids = p_op["id"];
- bool xf = p_op["flip_h"];
- bool yf = p_op["flip_v"];
- bool tr = p_op["transpose"];
-
- for (int i = 0; i < len; i++) {
- _set_cell(pr[i], ids, xf, yf, tr);
- node->make_bitmask_area_dirty(pr[i]);
- }
- if (!manual_autotile) {
- node->update_dirty_bitmask();
- }
-}
-
-void TileMapEditor::_erase_points(const Vector<Vector2> &p_points) {
- int len = p_points.size();
- const Vector2 *pr = p_points.ptr();
-
- for (int i = 0; i < len; i++) {
- _set_cell(pr[i], invalid_cell);
- }
-}
-
-void TileMapEditor::_select(const Point2i &p_from, const Point2i &p_to) {
- Point2i begin = p_from;
- Point2i end = p_to;
-
- if (begin.x > end.x) {
- SWAP(begin.x, end.x);
- }
- if (begin.y > end.y) {
- SWAP(begin.y, end.y);
- }
-
- rectangle.position = begin;
- rectangle.size = end - begin;
-
- CanvasItemEditor::get_singleton()->update_viewport();
-}
-
-void TileMapEditor::_erase_selection() {
- if (!selection_active) {
- return;
- }
-
- for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
- for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
- _set_cell(Point2i(j, i), invalid_cell, false, false, false);
- }
- }
-}
-
-void TileMapEditor::_draw_cell(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform) {
- Ref<Texture2D> t = node->get_tileset()->tile_get_texture(p_cell);
-
- if (t.is_null()) {
- return;
- }
-
- Vector2 tile_ofs = node->get_tileset()->tile_get_texture_offset(p_cell);
-
- Rect2 r = node->get_tileset()->tile_get_region(p_cell);
- if (node->get_tileset()->tile_get_tile_mode(p_cell) == TileSet::AUTO_TILE || node->get_tileset()->tile_get_tile_mode(p_cell) == TileSet::ATLAS_TILE) {
- Vector2 offset;
- if (tool != TOOL_PASTING) {
- int selected = manual_palette->get_current();
- if ((manual_autotile || (node->get_tileset()->tile_get_tile_mode(p_cell) == TileSet::ATLAS_TILE && !priority_atlastile)) && selected != -1) {
- offset = manual_palette->get_item_metadata(selected);
- } else {
- offset = node->get_tileset()->autotile_get_icon_coordinate(p_cell);
- }
- } else {
- offset = p_autotile_coord;
- }
-
- int spacing = node->get_tileset()->autotile_get_spacing(p_cell);
- r.size = node->get_tileset()->autotile_get_size(p_cell);
- r.position += (r.size + Vector2(spacing, spacing)) * offset;
- }
- Size2 cell_size = node->get_cell_size();
- bool centered_texture = node->is_centered_textures_enabled();
- bool compatibility_mode_enabled = node->is_compatibility_mode_enabled();
- Rect2 rect = Rect2();
- rect.position = node->map_to_world(p_point) + node->get_cell_draw_offset();
-
- if (r.has_no_area()) {
- rect.size = t->get_size();
- } else {
- rect.size = r.size;
- }
-
- if (compatibility_mode_enabled && !centered_texture) {
- if (rect.size.y > rect.size.x) {
- if ((p_flip_h && (p_flip_v || p_transpose)) || (p_flip_v && !p_transpose)) {
- tile_ofs.y += rect.size.y - rect.size.x;
- }
- } else if (rect.size.y < rect.size.x) {
- if ((p_flip_v && (p_flip_h || p_transpose)) || (p_flip_h && !p_transpose)) {
- tile_ofs.x += rect.size.x - rect.size.y;
- }
- }
- }
-
- if (p_transpose) {
- SWAP(tile_ofs.x, tile_ofs.y);
- if (centered_texture) {
- rect.position.x += cell_size.x / 2 - rect.size.y / 2;
- rect.position.y += cell_size.y / 2 - rect.size.x / 2;
- }
- } else if (centered_texture) {
- rect.position += cell_size / 2 - rect.size / 2;
- }
-
- if (p_flip_h) {
- rect.size.x *= -1.0;
- tile_ofs.x *= -1.0;
- }
-
- if (p_flip_v) {
- rect.size.y *= -1.0;
- tile_ofs.y *= -1.0;
- }
-
- if (compatibility_mode_enabled && !centered_texture) {
- if (node->get_tile_origin() == TileMap::TILE_ORIGIN_TOP_LEFT) {
- rect.position += tile_ofs;
- } else if (node->get_tile_origin() == TileMap::TILE_ORIGIN_BOTTOM_LEFT) {
- rect.position += tile_ofs;
-
- if (p_transpose) {
- if (p_flip_h) {
- rect.position.x -= cell_size.x;
- } else {
- rect.position.x += cell_size.x;
- }
- } else {
- if (p_flip_v) {
- rect.position.y -= cell_size.y;
- } else {
- rect.position.y += cell_size.y;
- }
- }
-
- } else if (node->get_tile_origin() == TileMap::TILE_ORIGIN_CENTER) {
- rect.position += tile_ofs;
-
- if (p_flip_h) {
- rect.position.x -= cell_size.x / 2;
- } else {
- rect.position.x += cell_size.x / 2;
- }
-
- if (p_flip_v) {
- rect.position.y -= cell_size.y / 2;
- } else {
- rect.position.y += cell_size.y / 2;
- }
- }
- } else {
- rect.position += tile_ofs;
- }
-
- Color modulate = node->get_tileset()->tile_get_modulate(p_cell);
- modulate.a = 0.5;
-
- Transform2D old_transform = p_viewport->get_viewport_transform();
- p_viewport->draw_set_transform_matrix(p_xform); // Take into account TileMap transformation when displaying cell
- if (r.has_no_area()) {
- p_viewport->draw_texture_rect(t, rect, false, modulate, p_transpose);
- } else {
- p_viewport->draw_texture_rect_region(t, rect, r, modulate, p_transpose);
- }
- p_viewport->draw_set_transform_matrix(old_transform);
-}
-
-void TileMapEditor::_draw_fill_preview(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform) {
- Vector<Vector2> points = _bucket_fill(p_point, false, true);
- const Vector2 *pr = points.ptr();
- int len = points.size();
-
- for (int i = 0; i < len; ++i) {
- _draw_cell(p_viewport, p_cell, pr[i], p_flip_h, p_flip_v, p_transpose, p_autotile_coord, p_xform);
- }
-}
-
-void TileMapEditor::_clear_bucket_cache() {
- if (bucket_cache_visited) {
- delete[] bucket_cache_visited;
- bucket_cache_visited = nullptr;
- }
-}
-
-void TileMapEditor::_update_copydata() {
- copydata.clear();
-
- if (!selection_active) {
- return;
- }
-
- for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
- for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
- TileData tcd;
-
- tcd.cell = node->get_cell(j, i);
- if (tcd.cell != TileMap::INVALID_CELL) {
- tcd.pos = Point2i(j, i);
- tcd.flip_h = node->is_cell_x_flipped(j, i);
- tcd.flip_v = node->is_cell_y_flipped(j, i);
- tcd.transpose = node->is_cell_transposed(j, i);
- tcd.autotile_coord = node->get_cell_autotile_coord(j, i);
-
- copydata.push_back(tcd);
- }
- }
- }
-}
-
-static inline Vector<Point2i> line(int x0, int x1, int y0, int y1) {
- Vector<Point2i> points;
-
- float dx = ABS(x1 - x0);
- float dy = ABS(y1 - y0);
-
- int x = x0;
- int y = y0;
-
- int sx = x0 > x1 ? -1 : 1;
- int sy = y0 > y1 ? -1 : 1;
-
- if (dx > dy) {
- float err = dx / 2;
-
- for (; x != x1; x += sx) {
- points.push_back(Vector2(x, y));
-
- err -= dy;
- if (err < 0) {
- y += sy;
- err += dx;
- }
- }
- } else {
- float err = dy / 2;
-
- for (; y != y1; y += sy) {
- points.push_back(Vector2(x, y));
-
- err -= dx;
- if (err < 0) {
- x += sx;
- err += dy;
- }
- }
- }
-
- points.push_back(Vector2(x, y));
-
- return points;
-}
-
-bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
- if (!node || !node->get_tileset().is_valid() || !node->is_visible_in_tree() || CanvasItemEditor::get_singleton()->get_current_tool() != CanvasItemEditor::TOOL_SELECT) {
- return false;
- }
-
- Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * node->get_global_transform();
- Transform2D xform_inv = xform.affine_inverse();
-
- Ref<InputEventMouseButton> mb = p_event;
-
- if (mb.is_valid()) {
- if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- if (mb->is_pressed()) {
- if (Input::get_singleton()->is_key_pressed(KEY_SPACE)) {
- return false; // Drag.
- }
-
- if (tool == TOOL_NONE) {
- tool = TOOL_PAINTING;
- _update_button_tool();
-
- if (mb->get_command()) {
- tool = TOOL_PICKING;
- _pick_tile(over_tile);
- _update_button_tool();
-
- return true;
- }
- }
-
- if (tool == TOOL_LINE_PAINT || tool == TOOL_RECTANGLE_PAINT) {
- selection_active = false;
- rectangle_begin = over_tile;
-
- mouse_down = true;
- } else if (tool == TOOL_PAINTING) {
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) {
- tool = TOOL_PAINTING;
-
- _start_undo(TTR("Paint TileMap"));
- }
- } else if (tool == TOOL_PICKING) {
- _pick_tile(over_tile);
- } else if (tool == TOOL_SELECTING) {
- selection_active = true;
- rectangle_begin = over_tile;
- }
-
- _update_button_tool();
- return true;
-
- } else {
- // Mousebutton was released.
- if (tool != TOOL_NONE) {
- if (tool == TOOL_PAINTING) {
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) {
- _set_cell(over_tile, ids, flip_h, flip_v, transpose);
- _finish_undo();
-
- paint_undo.clear();
- }
- } else if (tool == TOOL_LINE_PAINT) {
- if (!mouse_down) {
- return true;
- }
-
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) {
- _start_undo(TTR("Line Draw"));
- for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
- _set_cell(E->key(), ids, flip_h, flip_v, transpose);
- }
- _finish_undo();
-
- paint_undo.clear();
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- mouse_down = false;
- return true;
- }
-
- mouse_down = false;
- } else if (tool == TOOL_RECTANGLE_PAINT) {
- if (!mouse_down) {
- return true;
- }
-
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) {
- _start_undo(TTR("Rectangle Paint"));
- for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
- for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
- _set_cell(Point2i(j, i), ids, flip_h, flip_v, transpose);
- }
- }
- _finish_undo();
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- mouse_down = false;
- return true;
- }
-
- mouse_down = false;
- } else if (tool == TOOL_PASTING) {
- Point2 ofs = over_tile - rectangle.position;
- Vector<int> ids;
-
- _start_undo(TTR("Paste"));
- ids.push_back(0);
- for (List<TileData>::Element *E = copydata.front(); E; E = E->next()) {
- ids.write[0] = E->get().cell;
- _set_cell(E->get().pos + ofs, ids, E->get().flip_h, E->get().flip_v, E->get().transpose, E->get().autotile_coord);
- }
- _finish_undo();
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- return true; // We want to keep the Pasting tool.
- } else if (tool == TOOL_SELECTING) {
- CanvasItemEditor::get_singleton()->update_viewport();
-
- } else if (tool == TOOL_BUCKET) {
- Vector<Vector2> points = _bucket_fill(over_tile);
-
- if (points.size() == 0) {
- return false;
- }
-
- _start_undo(TTR("Bucket Fill"));
-
- Dictionary op;
- op["id"] = get_selected_tiles();
- op["flip_h"] = flip_h;
- op["flip_v"] = flip_v;
- op["transpose"] = transpose;
-
- _fill_points(points, op);
-
- _finish_undo();
-
- // So the fill preview is cleared right after the click.
- CanvasItemEditor::get_singleton()->update_viewport();
-
- // We want to keep the bucket-tool active.
- return true;
- }
-
- tool = TOOL_NONE;
- _update_button_tool();
-
- return true;
- }
- }
- } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
- if (mb->is_pressed()) {
- if (tool == TOOL_SELECTING || selection_active) {
- tool = TOOL_NONE;
- selection_active = false;
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
-
- if (tool == TOOL_PASTING) {
- tool = TOOL_NONE;
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
-
- if (tool == TOOL_LINE_PAINT) {
- tool = TOOL_LINE_ERASE;
- mouse_down = true;
- rectangle_begin = over_tile;
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
-
- if (tool == TOOL_RECTANGLE_PAINT) {
- tool = TOOL_RECTANGLE_ERASE;
- mouse_down = true;
- rectangle_begin = over_tile;
- copydata.clear();
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
-
- if (tool == TOOL_NONE) {
- paint_undo.clear();
-
- Point2 local = node->world_to_map(xform_inv.xform(mb->get_position()));
-
- _start_undo(TTR("Erase TileMap"));
- tool = TOOL_ERASING;
- _set_cell(local, invalid_cell);
-
- _update_button_tool();
- return true;
- }
-
- } else {
- if (tool == TOOL_LINE_ERASE) {
- if (!mouse_down) {
- return true;
- }
-
- tool = TOOL_LINE_PAINT;
- _update_button_tool();
-
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) {
- _start_undo(TTR("Line Erase"));
- for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
- _set_cell(E->key(), invalid_cell, flip_h, flip_v, transpose);
- }
- _finish_undo();
- paint_undo.clear();
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- mouse_down = false;
- return true;
- }
-
- mouse_down = false;
- } else if (tool == TOOL_RECTANGLE_ERASE) {
- if (!mouse_down) {
- return true;
- }
-
- tool = TOOL_RECTANGLE_PAINT;
- _update_button_tool();
-
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) {
- _start_undo(TTR("Rectangle Erase"));
- for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
- for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
- _set_cell(Point2i(j, i), invalid_cell, flip_h, flip_v, transpose);
- }
- }
- _finish_undo();
- paint_undo.clear();
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- mouse_down = false;
- return true;
- }
-
- mouse_down = false;
- tool = TOOL_RECTANGLE_PAINT;
- }
-
- if (tool == TOOL_ERASING) {
- tool = TOOL_NONE;
- _update_button_tool();
-
- return true;
- } else if (tool == TOOL_BUCKET) {
- Vector<int> ids;
- ids.push_back(node->get_cell(over_tile.x, over_tile.y));
- Dictionary pop;
- pop["id"] = ids;
- pop["flip_h"] = node->is_cell_x_flipped(over_tile.x, over_tile.y);
- pop["flip_v"] = node->is_cell_y_flipped(over_tile.x, over_tile.y);
- pop["transpose"] = node->is_cell_transposed(over_tile.x, over_tile.y);
-
- Vector<Vector2> points = _bucket_fill(over_tile, true);
-
- if (points.size() == 0) {
- return false;
- }
-
- undo_redo->create_action(TTR("Bucket Fill"));
-
- undo_redo->add_do_method(this, "_erase_points", points);
- undo_redo->add_undo_method(this, "_fill_points", points, pop);
-
- undo_redo->commit_action();
- }
- }
- }
- }
-
- Ref<InputEventMouseMotion> mm = p_event;
-
- if (mm.is_valid()) {
- Point2i new_over_tile = node->world_to_map(xform_inv.xform(mm->get_position()));
- Point2i old_over_tile = over_tile;
-
- if (new_over_tile != over_tile) {
- over_tile = new_over_tile;
- CanvasItemEditor::get_singleton()->update_viewport();
- }
-
- if (refocus_over_tile) {
- // editor lost focus; forget last tile position
- old_over_tile = new_over_tile;
- refocus_over_tile = false;
- }
-
- int tile_under = node->get_cell(over_tile.x, over_tile.y);
- String tile_name = "none";
-
- if (node->get_tileset()->has_tile(tile_under)) {
- tile_name = node->get_tileset()->tile_get_name(tile_under);
- }
- tile_info->show();
- tile_info->set_text(String::num(over_tile.x) + ", " + String::num(over_tile.y) + " [" + tile_name + "]");
-
- if (tool == TOOL_PAINTING) {
- // Paint using bresenham line to prevent holes in painting if the user moves fast.
-
- Vector<Point2i> points = line(old_over_tile.x, over_tile.x, old_over_tile.y, over_tile.y);
- Vector<int> ids = get_selected_tiles();
-
- for (int i = 0; i < points.size(); ++i) {
- Point2i pos = points[i];
-
- if (!paint_undo.has(pos)) {
- paint_undo[pos] = _get_op_from_cell(pos);
- }
-
- _set_cell(pos, ids, flip_h, flip_v, transpose);
- }
-
- return true;
- }
-
- if (tool == TOOL_ERASING) {
- // Erase using bresenham line to prevent holes in painting if the user moves fast.
-
- Vector<Point2i> points = line(old_over_tile.x, over_tile.x, old_over_tile.y, over_tile.y);
-
- for (int i = 0; i < points.size(); ++i) {
- Point2i pos = points[i];
-
- _set_cell(pos, invalid_cell);
- }
-
- return true;
- }
-
- if (tool == TOOL_SELECTING) {
- _select(rectangle_begin, over_tile);
-
- return true;
- }
-
- if (tool == TOOL_LINE_PAINT || tool == TOOL_LINE_ERASE) {
- Vector<int> ids = get_selected_tiles();
- Vector<int> tmp_cell;
- bool erasing = (tool == TOOL_LINE_ERASE);
-
- if (!mouse_down) {
- return true;
- }
-
- tmp_cell.push_back(0);
- if (erasing && paint_undo.size()) {
- for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
- tmp_cell.write[0] = E->get().idx;
- _set_cell(E->key(), tmp_cell, E->get().xf, E->get().yf, E->get().tr);
- }
- }
-
- paint_undo.clear();
-
- if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) {
- Vector<Point2i> points = line(rectangle_begin.x, over_tile.x, rectangle_begin.y, over_tile.y);
-
- for (int i = 0; i < points.size(); i++) {
- paint_undo[points[i]] = _get_op_from_cell(points[i]);
-
- if (erasing) {
- _set_cell(points[i], invalid_cell);
- }
- }
-
- CanvasItemEditor::get_singleton()->update_viewport();
- }
-
- return true;
- }
- if (tool == TOOL_RECTANGLE_PAINT || tool == TOOL_RECTANGLE_ERASE) {
- Vector<int> tmp_cell;
- tmp_cell.push_back(0);
-
- Point2i end_tile = over_tile;
-
- if (!mouse_down) {
- return true;
- }
-
- if (mm->get_shift()) {
- int size = fmax(ABS(end_tile.x - rectangle_begin.x), ABS(end_tile.y - rectangle_begin.y));
- int xDirection = MAX(MIN(end_tile.x - rectangle_begin.x, 1), -1);
- int yDirection = MAX(MIN(end_tile.y - rectangle_begin.y, 1), -1);
- end_tile = rectangle_begin + Point2i(xDirection * size, yDirection * size);
- }
-
- _select(rectangle_begin, end_tile);
-
- if (tool == TOOL_RECTANGLE_ERASE) {
- if (paint_undo.size()) {
- for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
- tmp_cell.write[0] = E->get().idx;
- _set_cell(E->key(), tmp_cell, E->get().xf, E->get().yf, E->get().tr);
- }
- }
-
- paint_undo.clear();
-
- for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
- for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
- Point2i tile = Point2i(j, i);
- paint_undo[tile] = _get_op_from_cell(tile);
-
- _set_cell(tile, invalid_cell);
- }
- }
- }
-
- return true;
- }
- if (tool == TOOL_PICKING && Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) {
- _pick_tile(over_tile);
-
- return true;
- }
- }
-
- Ref<InputEventKey> k = p_event;
-
- if (k.is_valid() && k->is_pressed()) {
- if (last_tool == TOOL_NONE && tool == TOOL_PICKING && k->get_keycode() == KEY_SHIFT && k->get_command()) {
- // trying to draw a rectangle with the painting tool, so change to the correct tool
- tool = last_tool;
-
- CanvasItemEditor::get_singleton()->update_viewport();
- _update_button_tool();
- }
-
- if (k->get_keycode() == KEY_ESCAPE) {
- if (tool == TOOL_PASTING) {
- copydata.clear();
- } else if (tool == TOOL_SELECTING || selection_active) {
- selection_active = false;
- }
-
- tool = TOOL_NONE;
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
-
- if (!mouse_over) {
- // Editor shortcuts should not fire if mouse not in viewport.
- return false;
- }
-
- if (ED_IS_SHORTCUT("tile_map_editor/paint_tile", p_event)) {
- // NOTE: We do not set tool = TOOL_PAINTING as this begins painting
- // immediately without pressing the left mouse button first.
- tool = TOOL_NONE;
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/line_fill", p_event)) {
- tool = TOOL_LINE_PAINT;
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/rectangle_fill", p_event)) {
- tool = TOOL_RECTANGLE_PAINT;
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/bucket_fill", p_event)) {
- tool = TOOL_BUCKET;
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/erase_selection", p_event)) {
- _menu_option(OPTION_ERASE_SELECTION);
-
- _update_button_tool();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/select", p_event)) {
- tool = TOOL_SELECTING;
- selection_active = false;
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/copy_selection", p_event)) {
- _update_copydata();
-
- if (selection_active) {
- tool = TOOL_PASTING;
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
- }
- if (ED_IS_SHORTCUT("tile_map_editor/cut_selection", p_event)) {
- if (selection_active) {
- _update_copydata();
-
- _start_undo(TTR("Cut Selection"));
- _erase_selection();
- _finish_undo();
-
- selection_active = false;
-
- tool = TOOL_PASTING;
-
- CanvasItemEditor::get_singleton()->update_viewport();
- _update_button_tool();
- return true;
- }
- }
- if (ED_IS_SHORTCUT("tile_map_editor/find_tile", p_event)) {
- search_box->select_all();
- search_box->grab_focus();
-
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/rotate_left", p_event)) {
- _rotate(-1);
- CanvasItemEditor::get_singleton()->update_viewport();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/rotate_right", p_event)) {
- _rotate(1);
- CanvasItemEditor::get_singleton()->update_viewport();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/flip_horizontal", p_event)) {
- _flip_horizontal();
- CanvasItemEditor::get_singleton()->update_viewport();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/flip_vertical", p_event)) {
- _flip_vertical();
- CanvasItemEditor::get_singleton()->update_viewport();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/clear_transform", p_event)) {
- _clear_transform();
- CanvasItemEditor::get_singleton()->update_viewport();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/transpose", p_event)) {
- transpose = !transpose;
- _update_palette();
- CanvasItemEditor::get_singleton()->update_viewport();
- return true;
- }
- } else if (k.is_valid()) { // Release event.
-
- if (tool == TOOL_NONE) {
- if (k->get_keycode() == KEY_SHIFT && k->get_command()) {
- tool = TOOL_PICKING;
- _update_button_tool();
- }
- } else if (tool == TOOL_PICKING) {
-#ifdef APPLE_STYLE_KEYS
- if (k->get_keycode() == KEY_META) {
-#else
- if (k->get_keycode() == KEY_CONTROL) {
-#endif
- // Go back to that last tool if KEY_CONTROL was released.
- tool = last_tool;
-
- CanvasItemEditor::get_singleton()->update_viewport();
- _update_button_tool();
- }
- }
- }
- return false;
-}
-
-void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
- if (!node || CanvasItemEditor::get_singleton()->get_current_tool() != CanvasItemEditor::TOOL_SELECT) {
- return;
- }
-
- Transform2D cell_xf = node->get_cell_transform();
- Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * node->get_global_transform();
- Transform2D xform_inv = xform.affine_inverse();
-
- Size2 screen_size = p_overlay->get_size();
- {
- Rect2 aabb;
- aabb.position = node->world_to_map(xform_inv.xform(Vector2()));
- aabb.expand_to(node->world_to_map(xform_inv.xform(Vector2(0, screen_size.height))));
- aabb.expand_to(node->world_to_map(xform_inv.xform(Vector2(screen_size.width, 0))));
- aabb.expand_to(node->world_to_map(xform_inv.xform(screen_size)));
- Rect2i si = aabb.grow(1.0);
-
- if (node->get_half_offset() != TileMap::HALF_OFFSET_X && node->get_half_offset() != TileMap::HALF_OFFSET_NEGATIVE_X) {
- int max_lines = 2000; //avoid crash if size too small
-
- for (int i = (si.position.x) - 1; i <= (si.position.x + si.size.x); i++) {
- Vector2 from = xform.xform(node->map_to_world(Vector2(i, si.position.y)));
- Vector2 to = xform.xform(node->map_to_world(Vector2(i, si.position.y + si.size.y + 1)));
-
- Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2);
- p_overlay->draw_line(from, to, col, 1);
- if (max_lines-- == 0) {
- break;
- }
- }
- } else {
- int max_lines = 10000; //avoid crash if size too small
-
- for (int i = (si.position.x) - 1; i <= (si.position.x + si.size.x); i++) {
- for (int j = (si.position.y) - 1; j <= (si.position.y + si.size.y); j++) {
- Vector2 ofs;
- if (ABS(j) & 1) {
- ofs = cell_xf[0] * (node->get_half_offset() == TileMap::HALF_OFFSET_X ? 0.5 : -0.5);
- }
-
- Vector2 from = xform.xform(node->map_to_world(Vector2(i, j), true) + ofs);
- Vector2 to = xform.xform(node->map_to_world(Vector2(i, j + 1), true) + ofs);
-
- Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2);
- p_overlay->draw_line(from, to, col, 1);
-
- if (--max_lines == 0) {
- break;
- }
- }
- if (max_lines == 0) {
- break;
- }
- }
- }
-
- int max_lines = 10000; //avoid crash if size too small
-
- if (node->get_half_offset() != TileMap::HALF_OFFSET_Y && node->get_half_offset() != TileMap::HALF_OFFSET_NEGATIVE_Y) {
- for (int i = (si.position.y) - 1; i <= (si.position.y + si.size.y); i++) {
- Vector2 from = xform.xform(node->map_to_world(Vector2(si.position.x, i)));
- Vector2 to = xform.xform(node->map_to_world(Vector2(si.position.x + si.size.x + 1, i)));
-
- Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2);
- p_overlay->draw_line(from, to, col, 1);
-
- if (max_lines-- == 0) {
- break;
- }
- }
- } else {
- for (int i = (si.position.y) - 1; i <= (si.position.y + si.size.y); i++) {
- for (int j = (si.position.x) - 1; j <= (si.position.x + si.size.x); j++) {
- Vector2 ofs;
- if (ABS(j) & 1) {
- ofs = cell_xf[1] * (node->get_half_offset() == TileMap::HALF_OFFSET_Y ? 0.5 : -0.5);
- }
-
- Vector2 from = xform.xform(node->map_to_world(Vector2(j, i), true) + ofs);
- Vector2 to = xform.xform(node->map_to_world(Vector2(j + 1, i), true) + ofs);
-
- Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2);
- p_overlay->draw_line(from, to, col, 1);
-
- if (--max_lines == 0) {
- break;
- }
- }
- if (max_lines == 0) {
- break;
- }
- }
- }
- }
-
- if (selection_active) {
- Vector<Vector2> points;
- points.push_back(xform.xform(node->map_to_world((rectangle.position))));
- points.push_back(xform.xform(node->map_to_world((rectangle.position + Point2(rectangle.size.x + 1, 0)))));
- points.push_back(xform.xform(node->map_to_world((rectangle.position + Point2(rectangle.size.x + 1, rectangle.size.y + 1)))));
- points.push_back(xform.xform(node->map_to_world((rectangle.position + Point2(0, rectangle.size.y + 1)))));
-
- p_overlay->draw_colored_polygon(points, Color(0.2, 0.8, 1, 0.4));
- }
-
- if (mouse_over && node->get_tileset().is_valid()) {
- Vector2 endpoints[4] = {
- node->map_to_world(over_tile, true),
- node->map_to_world((over_tile + Point2(1, 0)), true),
- node->map_to_world((over_tile + Point2(1, 1)), true),
- node->map_to_world((over_tile + Point2(0, 1)), true)
- };
-
- for (int i = 0; i < 4; i++) {
- if (node->get_half_offset() == TileMap::HALF_OFFSET_X && ABS(over_tile.y) & 1) {
- endpoints[i] += cell_xf[0] * 0.5;
- }
- if (node->get_half_offset() == TileMap::HALF_OFFSET_NEGATIVE_X && ABS(over_tile.y) & 1) {
- endpoints[i] += cell_xf[0] * -0.5;
- }
- if (node->get_half_offset() == TileMap::HALF_OFFSET_Y && ABS(over_tile.x) & 1) {
- endpoints[i] += cell_xf[1] * 0.5;
- }
- if (node->get_half_offset() == TileMap::HALF_OFFSET_NEGATIVE_Y && ABS(over_tile.x) & 1) {
- endpoints[i] += cell_xf[1] * -0.5;
- }
- endpoints[i] = xform.xform(endpoints[i]);
- }
- Color col;
- if (node->get_cell(over_tile.x, over_tile.y) != TileMap::INVALID_CELL) {
- col = Color(0.2, 0.8, 1.0, 0.8);
- } else {
- col = Color(1.0, 0.4, 0.2, 0.8);
- }
-
- for (int i = 0; i < 4; i++) {
- p_overlay->draw_line(endpoints[i], endpoints[(i + 1) % 4], col, 2);
- }
-
- bool bucket_preview = EditorSettings::get_singleton()->get("editors/tile_map/bucket_fill_preview");
- if (tool == TOOL_SELECTING || tool == TOOL_PICKING || !bucket_preview) {
- return;
- }
-
- if (tool == TOOL_LINE_PAINT) {
- if (!mouse_down) {
- return;
- }
-
- if (paint_undo.is_empty()) {
- return;
- }
-
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() == 1 && ids[0] == TileMap::INVALID_CELL) {
- return;
- }
-
- for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
- _draw_cell(p_overlay, ids[0], E->key(), flip_h, flip_v, transpose, autotile_coord, xform);
- }
-
- } else if (tool == TOOL_RECTANGLE_PAINT) {
- if (!mouse_down) {
- return;
- }
-
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() == 1 && ids[0] == TileMap::INVALID_CELL) {
- return;
- }
-
- for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
- for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
- _draw_cell(p_overlay, ids[0], Point2i(j, i), flip_h, flip_v, transpose, autotile_coord, xform);
- }
- }
- } else if (tool == TOOL_PASTING) {
- if (copydata.is_empty()) {
- return;
- }
-
- Ref<TileSet> ts = node->get_tileset();
-
- if (ts.is_null()) {
- return;
- }
-
- Point2 ofs = over_tile - rectangle.position;
-
- for (List<TileData>::Element *E = copydata.front(); E; E = E->next()) {
- if (!ts->has_tile(E->get().cell)) {
- continue;
- }
-
- TileData tcd = E->get();
-
- _draw_cell(p_overlay, tcd.cell, tcd.pos + ofs, tcd.flip_h, tcd.flip_v, tcd.transpose, tcd.autotile_coord, xform);
- }
-
- Rect2i duplicate = rectangle;
- duplicate.position = over_tile;
-
- Vector<Vector2> points;
- points.push_back(xform.xform(node->map_to_world(duplicate.position)));
- points.push_back(xform.xform(node->map_to_world((duplicate.position + Point2(duplicate.size.x + 1, 0)))));
- points.push_back(xform.xform(node->map_to_world((duplicate.position + Point2(duplicate.size.x + 1, duplicate.size.y + 1)))));
- points.push_back(xform.xform(node->map_to_world((duplicate.position + Point2(0, duplicate.size.y + 1)))));
-
- p_overlay->draw_colored_polygon(points, Color(0.2, 1.0, 0.8, 0.2));
-
- } else if (tool == TOOL_BUCKET) {
- Vector<int> tiles = get_selected_tiles();
- _draw_fill_preview(p_overlay, tiles[0], over_tile, flip_h, flip_v, transpose, autotile_coord, xform);
-
- } else {
- Vector<int> st = get_selected_tiles();
-
- if (st.size() == 1 && st[0] == TileMap::INVALID_CELL) {
- return;
- }
-
- _draw_cell(p_overlay, st[0], over_tile, flip_h, flip_v, transpose, autotile_coord, xform);
- }
- }
-}
-
-void TileMapEditor::edit(Node *p_tile_map) {
- search_box->set_text("");
-
- if (!canvas_item_editor_viewport) {
- canvas_item_editor_viewport = CanvasItemEditor::get_singleton()->get_viewport_control();
- }
-
- if (node) {
- Callable callable_tileset_settings_changed = callable_mp(this, &TileMapEditor::_tileset_settings_changed);
-
- if (node->is_connected("settings_changed", callable_tileset_settings_changed)) {
- node->disconnect("settings_changed", callable_tileset_settings_changed);
- }
- }
- if (p_tile_map) {
- node = Object::cast_to<TileMap>(p_tile_map);
- if (!canvas_item_editor_viewport->is_connected("mouse_entered", callable_mp(this, &TileMapEditor::_canvas_mouse_enter))) {
- canvas_item_editor_viewport->connect("mouse_entered", callable_mp(this, &TileMapEditor::_canvas_mouse_enter));
- }
- if (!canvas_item_editor_viewport->is_connected("mouse_exited", callable_mp(this, &TileMapEditor::_canvas_mouse_exit))) {
- canvas_item_editor_viewport->connect("mouse_exited", callable_mp(this, &TileMapEditor::_canvas_mouse_exit));
- }
-
- _update_palette();
-
- } else {
- node = nullptr;
-
- if (canvas_item_editor_viewport->is_connected("mouse_entered", callable_mp(this, &TileMapEditor::_canvas_mouse_enter))) {
- canvas_item_editor_viewport->disconnect("mouse_entered", callable_mp(this, &TileMapEditor::_canvas_mouse_enter));
- }
- if (canvas_item_editor_viewport->is_connected("mouse_exited", callable_mp(this, &TileMapEditor::_canvas_mouse_exit))) {
- canvas_item_editor_viewport->disconnect("mouse_exited", callable_mp(this, &TileMapEditor::_canvas_mouse_exit));
- }
-
- _update_palette();
- }
-
- if (node) {
- Callable callable_tileset_settings_changed = callable_mp(this, &TileMapEditor::_tileset_settings_changed);
-
- if (!node->is_connected("settings_changed", callable_tileset_settings_changed)) {
- node->connect("settings_changed", callable_tileset_settings_changed);
- }
- }
-
- _clear_bucket_cache();
-}
-
-void TileMapEditor::_tileset_settings_changed() {
- _update_palette();
- CanvasItemEditor::get_singleton()->update_viewport();
-}
-
-void TileMapEditor::_icon_size_changed(float p_value) {
- if (node) {
- palette->set_icon_scale(p_value);
- manual_palette->set_icon_scale(p_value);
- _update_palette();
- }
-}
-
-void TileMapEditor::_bind_methods() {
- ClassDB::bind_method(D_METHOD("_fill_points"), &TileMapEditor::_fill_points);
- ClassDB::bind_method(D_METHOD("_erase_points"), &TileMapEditor::_erase_points);
-}
-
-TileMapEditor::CellOp TileMapEditor::_get_op_from_cell(const Point2i &p_pos) {
- CellOp op;
- op.idx = node->get_cell(p_pos.x, p_pos.y);
- if (op.idx != TileMap::INVALID_CELL) {
- if (node->is_cell_x_flipped(p_pos.x, p_pos.y)) {
- op.xf = true;
- }
- if (node->is_cell_y_flipped(p_pos.x, p_pos.y)) {
- op.yf = true;
- }
- if (node->is_cell_transposed(p_pos.x, p_pos.y)) {
- op.tr = true;
- }
- op.ac = node->get_cell_autotile_coord(p_pos.x, p_pos.y);
- }
- return op;
-}
-
-void TileMapEditor::_rotate(int steps) {
- const bool normal_rotation_matrix[][3] = {
- { false, false, false },
- { true, true, false },
- { false, true, true },
- { true, false, true }
- };
-
- const bool mirrored_rotation_matrix[][3] = {
- { false, true, false },
- { true, true, true },
- { false, false, true },
- { true, false, false }
- };
-
- if (transpose ^ flip_h ^ flip_v) {
- // Odd number of flags activated = mirrored rotation
- for (int i = 0; i < 4; i++) {
- if (transpose == mirrored_rotation_matrix[i][0] &&
- flip_h == mirrored_rotation_matrix[i][1] &&
- flip_v == mirrored_rotation_matrix[i][2]) {
- int new_id = Math::wrapi(i + steps, 0, 4);
- transpose = mirrored_rotation_matrix[new_id][0];
- flip_h = mirrored_rotation_matrix[new_id][1];
- flip_v = mirrored_rotation_matrix[new_id][2];
- break;
- }
- }
- } else {
- // Even number of flags activated = normal rotation
- for (int i = 0; i < 4; i++) {
- if (transpose == normal_rotation_matrix[i][0] &&
- flip_h == normal_rotation_matrix[i][1] &&
- flip_v == normal_rotation_matrix[i][2]) {
- int new_id = Math::wrapi(i + steps, 0, 4);
- transpose = normal_rotation_matrix[new_id][0];
- flip_h = normal_rotation_matrix[new_id][1];
- flip_v = normal_rotation_matrix[new_id][2];
- break;
- }
- }
- }
-
- _update_palette();
-}
-
-void TileMapEditor::_flip_horizontal() {
- flip_h = !flip_h;
- _update_palette();
-}
-
-void TileMapEditor::_flip_vertical() {
- flip_v = !flip_v;
- _update_palette();
-}
-
-void TileMapEditor::_clear_transform() {
- transpose = false;
- flip_h = false;
- flip_v = false;
- _update_palette();
-}
-
-TileMapEditor::TileMapEditor(EditorNode *p_editor) {
- node = nullptr;
- manual_autotile = false;
- priority_atlastile = false;
- manual_position = Vector2(0, 0);
- canvas_item_editor_viewport = nullptr;
- editor = p_editor;
- undo_redo = EditorNode::get_undo_redo();
-
- tool = TOOL_NONE;
- selection_active = false;
- mouse_over = false;
- mouse_down = false;
-
- flip_h = false;
- flip_v = false;
- transpose = false;
-
- bucket_cache_tile = -1;
- bucket_cache_visited = nullptr;
-
- invalid_cell.resize(1);
- invalid_cell.write[0] = TileMap::INVALID_CELL;
-
- ED_SHORTCUT("tile_map_editor/erase_selection", TTR("Erase Selection"), KEY_DELETE);
- ED_SHORTCUT("tile_map_editor/find_tile", TTR("Find Tile"), KEY_MASK_CMD + KEY_F);
- ED_SHORTCUT("tile_map_editor/transpose", TTR("Transpose"), KEY_T);
-
- HBoxContainer *tool_hb = memnew(HBoxContainer);
- add_child(tool_hb);
-
- manual_button = memnew(CheckBox);
- manual_button->set_text(TTR("Disable Autotile"));
- manual_button->connect("toggled", callable_mp(this, &TileMapEditor::_manual_toggled));
- add_child(manual_button);
-
- priority_button = memnew(CheckBox);
- priority_button->set_text(TTR("Enable Priority"));
- priority_button->connect("toggled", callable_mp(this, &TileMapEditor::_priority_toggled));
- add_child(priority_button);
-
- search_box = memnew(LineEdit);
- search_box->set_placeholder(TTR("Filter tiles"));
- search_box->set_h_size_flags(SIZE_EXPAND_FILL);
- search_box->connect("text_entered", callable_mp(this, &TileMapEditor::_text_entered));
- search_box->connect("text_changed", callable_mp(this, &TileMapEditor::_text_changed));
- search_box->connect("gui_input", callable_mp(this, &TileMapEditor::_sbox_input));
- add_child(search_box);
-
- size_slider = memnew(HSlider);
- size_slider->set_h_size_flags(SIZE_EXPAND_FILL);
- size_slider->set_min(0.1f);
- size_slider->set_max(4.0f);
- size_slider->set_step(0.1f);
- size_slider->set_value(1.0f);
- size_slider->connect("value_changed", callable_mp(this, &TileMapEditor::_icon_size_changed));
- add_child(size_slider);
-
- int mw = EDITOR_DEF("editors/tile_map/palette_min_width", 80);
-
- VSplitContainer *palette_container = memnew(VSplitContainer);
- palette_container->set_v_size_flags(SIZE_EXPAND_FILL);
- palette_container->set_custom_minimum_size(Size2(mw, 0));
- add_child(palette_container);
-
- // Add tile palette.
- palette = memnew(ItemList);
- palette->set_h_size_flags(SIZE_EXPAND_FILL);
- palette->set_v_size_flags(SIZE_EXPAND_FILL);
- palette->set_max_columns(0);
- palette->set_icon_mode(ItemList::ICON_MODE_TOP);
- palette->set_max_text_lines(2);
- palette->set_select_mode(ItemList::SELECT_MULTI);
- palette->add_theme_constant_override("vseparation", 8 * EDSCALE);
- palette->connect("item_selected", callable_mp(this, &TileMapEditor::_palette_selected));
- palette->connect("multi_selected", callable_mp(this, &TileMapEditor::_palette_multi_selected));
- palette->connect("gui_input", callable_mp(this, &TileMapEditor::_palette_input));
- palette_container->add_child(palette);
-
- // Add message for when no texture is selected.
- info_message = memnew(Label);
- info_message->set_text(TTR("Give a TileSet resource to this TileMap to use its tiles."));
- info_message->set_valign(Label::VALIGN_CENTER);
- info_message->set_align(Label::ALIGN_CENTER);
- info_message->set_autowrap(true);
- info_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
- info_message->set_anchors_and_offsets_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE);
- palette->add_child(info_message);
-
- // Add autotile override palette.
- manual_palette = memnew(ItemList);
- manual_palette->set_h_size_flags(SIZE_EXPAND_FILL);
- manual_palette->set_v_size_flags(SIZE_EXPAND_FILL);
- manual_palette->set_max_columns(0);
- manual_palette->set_icon_mode(ItemList::ICON_MODE_TOP);
- manual_palette->set_max_text_lines(2);
- manual_palette->hide();
- palette_container->add_child(manual_palette);
-
- // Add menu items.
- toolbar = memnew(HBoxContainer);
- toolbar->hide();
- CanvasItemEditor::get_singleton()->add_control_to_menu_panel(toolbar);
-
- toolbar->add_child(memnew(VSeparator));
-
- // Tools.
- paint_button = memnew(Button);
- paint_button->set_flat(true);
- paint_button->set_shortcut(ED_SHORTCUT("tile_map_editor/paint_tile", TTR("Paint Tile"), KEY_P));
- paint_button->set_shortcut_context(this);
- paint_button->set_tooltip(TTR("RMB: Erase"));
- paint_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_NONE));
- paint_button->set_toggle_mode(true);
- toolbar->add_child(paint_button);
-
- line_button = memnew(Button);
- line_button->set_flat(true);
- line_button->set_shortcut(ED_SHORTCUT("tile_map_editor/line_fill", TTR("Line Fill"), KEY_L));
- line_button->set_shortcut_context(this);
- line_button->set_tooltip(TTR("RMB: Erase"));
- line_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_LINE_PAINT));
- line_button->set_toggle_mode(true);
- toolbar->add_child(line_button);
-
- rectangle_button = memnew(Button);
- rectangle_button->set_flat(true);
- rectangle_button->set_shortcut(ED_SHORTCUT("tile_map_editor/rectangle_fill", TTR("Rectangle Fill"), KEY_O));
- rectangle_button->set_shortcut_context(this);
- rectangle_button->set_tooltip(TTR("Shift+LMB: Keep 1:1 proporsions\nRMB: Erase"));
- rectangle_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_RECTANGLE_PAINT));
- rectangle_button->set_toggle_mode(true);
- toolbar->add_child(rectangle_button);
-
- bucket_fill_button = memnew(Button);
- bucket_fill_button->set_flat(true);
- bucket_fill_button->set_shortcut(ED_SHORTCUT("tile_map_editor/bucket_fill", TTR("Bucket Fill"), KEY_B));
- bucket_fill_button->set_shortcut_context(this);
- bucket_fill_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_BUCKET));
- bucket_fill_button->set_toggle_mode(true);
- toolbar->add_child(bucket_fill_button);
-
- picker_button = memnew(Button);
- picker_button->set_flat(true);
- picker_button->set_shortcut(ED_SHORTCUT("tile_map_editor/pick_tile", TTR("Pick Tile"), KEY_I));
- picker_button->set_shortcut_context(this);
- picker_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_PICKING));
- picker_button->set_toggle_mode(true);
- toolbar->add_child(picker_button);
-
- select_button = memnew(Button);
- select_button->set_flat(true);
- select_button->set_shortcut(ED_SHORTCUT("tile_map_editor/select", TTR("Select"), KEY_M));
- select_button->set_shortcut_context(this);
- select_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_SELECTING));
- select_button->set_toggle_mode(true);
- toolbar->add_child(select_button);
-
- _update_button_tool();
-
- // Container to the right of the toolbar.
- toolbar_right = memnew(HBoxContainer);
- toolbar_right->hide();
- toolbar_right->set_h_size_flags(SIZE_EXPAND_FILL);
- toolbar_right->set_alignment(BoxContainer::ALIGN_END);
- CanvasItemEditor::get_singleton()->add_control_to_menu_panel(toolbar_right);
-
- // Tile position.
- tile_info = memnew(Label);
- tile_info->set_modulate(Color(1, 1, 1, 0.8));
- tile_info->set_mouse_filter(MOUSE_FILTER_IGNORE);
- tile_info->add_theme_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_theme_font("main", "EditorFonts"));
- tile_info->add_theme_font_size_override("font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size("main_size", "EditorFonts"));
- // The tile info is only displayed after a tile has been hovered.
- tile_info->hide();
- CanvasItemEditor::get_singleton()->add_control_to_info_overlay(tile_info);
-
- // Menu.
- options = memnew(MenuButton);
- options->set_shortcut_context(this);
- options->set_text("TileMap");
- options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("TileMap", "EditorIcons"));
- toolbar_right->add_child(options);
-
- PopupMenu *p = options->get_popup();
- p->add_shortcut(ED_SHORTCUT("tile_map_editor/cut_selection", TTR("Cut Selection"), KEY_MASK_CMD + KEY_X), OPTION_CUT);
- p->add_shortcut(ED_SHORTCUT("tile_map_editor/copy_selection", TTR("Copy Selection"), KEY_MASK_CMD + KEY_C), OPTION_COPY);
- p->add_shortcut(ED_GET_SHORTCUT("tile_map_editor/erase_selection"), OPTION_ERASE_SELECTION);
- p->add_separator();
- p->add_item(TTR("Fix Invalid Tiles"), OPTION_FIX_INVALID);
- p->connect("id_pressed", callable_mp(this, &TileMapEditor::_menu_option));
-
- rotate_left_button = memnew(Button);
- rotate_left_button->set_flat(true);
- rotate_left_button->set_tooltip(TTR("Rotate Left"));
- rotate_left_button->set_focus_mode(FOCUS_NONE);
- rotate_left_button->connect("pressed", callable_mp(this, &TileMapEditor::_rotate), varray(-1));
- rotate_left_button->set_shortcut(ED_SHORTCUT("tile_map_editor/rotate_left", TTR("Rotate Left"), KEY_A));
- rotate_left_button->set_shortcut_context(this);
- tool_hb->add_child(rotate_left_button);
-
- rotate_right_button = memnew(Button);
- rotate_right_button->set_flat(true);
- rotate_right_button->set_tooltip(TTR("Rotate Right"));
- rotate_right_button->set_focus_mode(FOCUS_NONE);
- rotate_right_button->connect("pressed", callable_mp(this, &TileMapEditor::_rotate), varray(1));
- rotate_right_button->set_shortcut(ED_SHORTCUT("tile_map_editor/rotate_right", TTR("Rotate Right"), KEY_S));
- rotate_right_button->set_shortcut_context(this);
- tool_hb->add_child(rotate_right_button);
-
- flip_horizontal_button = memnew(Button);
- flip_horizontal_button->set_flat(true);
- flip_horizontal_button->set_tooltip(TTR("Flip Horizontally"));
- flip_horizontal_button->set_focus_mode(FOCUS_NONE);
- flip_horizontal_button->connect("pressed", callable_mp(this, &TileMapEditor::_flip_horizontal));
- flip_horizontal_button->set_shortcut(ED_SHORTCUT("tile_map_editor/flip_horizontal", TTR("Flip Horizontally"), KEY_X));
- flip_horizontal_button->set_shortcut_context(this);
- tool_hb->add_child(flip_horizontal_button);
-
- flip_vertical_button = memnew(Button);
- flip_vertical_button->set_flat(true);
- flip_vertical_button->set_tooltip(TTR("Flip Vertically"));
- flip_vertical_button->set_focus_mode(FOCUS_NONE);
- flip_vertical_button->connect("pressed", callable_mp(this, &TileMapEditor::_flip_vertical));
- flip_vertical_button->set_shortcut(ED_SHORTCUT("tile_map_editor/flip_vertical", TTR("Flip Vertically"), KEY_Z));
- flip_vertical_button->set_shortcut_context(this);
- tool_hb->add_child(flip_vertical_button);
-
- clear_transform_button = memnew(Button);
- clear_transform_button->set_flat(true);
- clear_transform_button->set_tooltip(TTR("Clear Transform"));
- clear_transform_button->set_focus_mode(FOCUS_NONE);
- clear_transform_button->connect("pressed", callable_mp(this, &TileMapEditor::_clear_transform));
- clear_transform_button->set_shortcut(ED_SHORTCUT("tile_map_editor/clear_transform", TTR("Clear Transform"), KEY_W));
- clear_transform_button->set_shortcut_context(this);
- tool_hb->add_child(clear_transform_button);
-
- clear_transform_button->set_disabled(true);
-}
-
-TileMapEditor::~TileMapEditor() {
- _clear_bucket_cache();
- copydata.clear();
-}
-
-///////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////
-
-void TileMapEditorPlugin::_notification(int p_what) {
- if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
- switch ((int)EditorSettings::get_singleton()->get("editors/tile_map/editor_side")) {
- case 0: { // Left.
- CanvasItemEditor::get_singleton()->get_palette_split()->move_child(tile_map_editor, 0);
- } break;
- case 1: { // Right.
- CanvasItemEditor::get_singleton()->get_palette_split()->move_child(tile_map_editor, 1);
- } break;
- }
- }
-}
-
-void TileMapEditorPlugin::edit(Object *p_object) {
- tile_map_editor->edit(Object::cast_to<Node>(p_object));
-}
-
-bool TileMapEditorPlugin::handles(Object *p_object) const {
- return p_object->is_class("TileMap");
-}
-
-void TileMapEditorPlugin::make_visible(bool p_visible) {
- if (p_visible) {
- tile_map_editor->show();
- tile_map_editor->get_toolbar()->show();
- tile_map_editor->get_toolbar_right()->show();
- // `tile_info` isn't shown here, as it's displayed after a tile has been hovered.
- // Otherwise, a translucent black rectangle would be visible as there would be an
- // empty Label in the CanvasItemEditor's info overlay.
-
- // Change to TOOL_SELECT when TileMap node is selected, to prevent accidental movement.
- CanvasItemEditor::get_singleton()->set_current_tool(CanvasItemEditor::TOOL_SELECT);
- } else {
- tile_map_editor->hide();
- tile_map_editor->get_toolbar()->hide();
- tile_map_editor->get_toolbar_right()->hide();
- tile_map_editor->get_tile_info()->hide();
- tile_map_editor->edit(nullptr);
- }
-}
-
-TileMapEditorPlugin::TileMapEditorPlugin(EditorNode *p_node) {
- EDITOR_DEF("editors/tile_map/preview_size", 64);
- EDITOR_DEF("editors/tile_map/palette_item_hseparation", 8);
- EDITOR_DEF("editors/tile_map/show_tile_names", true);
- EDITOR_DEF("editors/tile_map/show_tile_ids", false);
- EDITOR_DEF("editors/tile_map/sort_tiles_by_name", true);
- EDITOR_DEF("editors/tile_map/bucket_fill_preview", true);
- EDITOR_DEF("editors/tile_map/editor_side", 1);
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/tile_map/editor_side", PROPERTY_HINT_ENUM, "Left,Right"));
-
- tile_map_editor = memnew(TileMapEditor(p_node));
- switch ((int)EditorSettings::get_singleton()->get("editors/tile_map/editor_side")) {
- case 0: { // Left.
- add_control_to_container(CONTAINER_CANVAS_EDITOR_SIDE_LEFT, tile_map_editor);
- } break;
- case 1: { // Right.
- add_control_to_container(CONTAINER_CANVAS_EDITOR_SIDE_RIGHT, tile_map_editor);
- } break;
- }
- tile_map_editor->hide();
-}
-
-TileMapEditorPlugin::~TileMapEditorPlugin() {
-}
diff --git a/editor/plugins/tile_map_editor_plugin.h b/editor/plugins/tile_map_editor_plugin.h
deleted file mode 100644
index 421a3b3f68..0000000000
--- a/editor/plugins/tile_map_editor_plugin.h
+++ /dev/null
@@ -1,242 +0,0 @@
-/*************************************************************************/
-/* tile_map_editor_plugin.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_MAP_EDITOR_PLUGIN_H
-#define TILE_MAP_EDITOR_PLUGIN_H
-
-#include "editor/editor_node.h"
-#include "editor/editor_plugin.h"
-#include "scene/2d/tile_map.h"
-#include "scene/gui/check_box.h"
-#include "scene/gui/label.h"
-#include "scene/gui/line_edit.h"
-#include "scene/gui/menu_button.h"
-
-class TileMapEditor : public VBoxContainer {
- GDCLASS(TileMapEditor, VBoxContainer);
-
- enum Tool {
- TOOL_NONE,
- TOOL_PAINTING,
- TOOL_ERASING,
- TOOL_RECTANGLE_PAINT,
- TOOL_RECTANGLE_ERASE,
- TOOL_LINE_PAINT,
- TOOL_LINE_ERASE,
- TOOL_SELECTING,
- TOOL_BUCKET,
- TOOL_PICKING,
- TOOL_PASTING
- };
-
- enum Options {
- OPTION_COPY,
- OPTION_ERASE_SELECTION,
- OPTION_FIX_INVALID,
- OPTION_CUT
- };
-
- TileMap *node;
- bool manual_autotile;
- bool priority_atlastile;
- Vector2 manual_position;
-
- EditorNode *editor;
- UndoRedo *undo_redo;
- Control *canvas_item_editor_viewport;
-
- LineEdit *search_box;
- HSlider *size_slider;
- ItemList *palette;
- ItemList *manual_palette;
-
- Label *info_message;
-
- HBoxContainer *toolbar;
- HBoxContainer *toolbar_right;
-
- Label *tile_info;
- MenuButton *options;
-
- Button *paint_button;
- Button *line_button;
- Button *rectangle_button;
- Button *bucket_fill_button;
- Button *picker_button;
- Button *select_button;
-
- Button *flip_horizontal_button;
- Button *flip_vertical_button;
- Button *rotate_left_button;
- Button *rotate_right_button;
- Button *clear_transform_button;
-
- CheckBox *manual_button;
- CheckBox *priority_button;
-
- Tool tool;
- Tool last_tool;
-
- bool selection_active;
- bool mouse_over;
- bool mouse_down;
-
- bool flip_h;
- bool flip_v;
- bool transpose;
- Point2i autotile_coord;
-
- Point2i rectangle_begin;
- Rect2i rectangle;
-
- Point2i over_tile;
- bool refocus_over_tile = false;
-
- bool *bucket_cache_visited;
- Rect2i bucket_cache_rect;
- int bucket_cache_tile;
- Vector<Vector2> bucket_cache;
- List<Point2i> bucket_queue;
-
- struct CellOp {
- int idx = TileMap::INVALID_CELL;
- bool xf = false;
- bool yf = false;
- bool tr = false;
- Vector2 ac;
- };
-
- Map<Point2i, CellOp> paint_undo;
-
- struct TileData {
- Point2i pos;
- int cell = TileMap::INVALID_CELL;
- bool flip_h = false;
- bool flip_v = false;
- bool transpose = false;
- Point2i autotile_coord;
- };
-
- List<TileData> copydata;
-
- Map<Point2i, CellOp> undo_data;
- Vector<int> invalid_cell;
-
- void _pick_tile(const Point2 &p_pos);
-
- Vector<Vector2> _bucket_fill(const Point2i &p_start, bool erase = false, bool preview = false);
-
- void _fill_points(const Vector<Vector2> &p_points, const Dictionary &p_op);
- void _erase_points(const Vector<Vector2> &p_points);
-
- void _select(const Point2i &p_from, const Point2i &p_to);
- void _erase_selection();
-
- void _draw_cell(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform);
- void _draw_fill_preview(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform);
- void _clear_bucket_cache();
-
- void _update_copydata();
-
- Vector<int> get_selected_tiles() const;
- void set_selected_tiles(Vector<int> p_tile);
-
- void _manual_toggled(bool p_enabled);
- void _priority_toggled(bool p_enabled);
- void _text_entered(const String &p_text);
- void _text_changed(const String &p_text);
- void _sbox_input(const Ref<InputEvent> &p_ie);
- void _update_palette();
- void _update_button_tool();
- void _button_tool_select(int p_tool);
- void _menu_option(int p_option);
- void _palette_selected(int index);
- void _palette_multi_selected(int index, bool selected);
- void _palette_input(const Ref<InputEvent> &p_event);
-
- Dictionary _create_cell_dictionary(int tile, bool flip_x, bool flip_y, bool transpose, Vector2 autotile_coord);
- void _start_undo(const String &p_action);
- void _finish_undo();
- void _create_set_cell_undo_redo(const Vector2 &p_vec, const CellOp &p_cell_old, const CellOp &p_cell_new);
- void _set_cell(const Point2i &p_pos, Vector<int> p_values, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false, const Point2i &p_autotile_coord = Point2());
-
- void _canvas_mouse_enter();
- void _canvas_mouse_exit();
- void _tileset_settings_changed();
- void _icon_size_changed(float p_value);
-
- void _clear_transform();
- void _flip_horizontal();
- void _flip_vertical();
- void _rotate(int steps);
-
-protected:
- void _notification(int p_what);
- void _node_removed(Node *p_node);
- static void _bind_methods();
- CellOp _get_op_from_cell(const Point2i &p_pos);
-
-public:
- HBoxContainer *get_toolbar() const { return toolbar; }
- HBoxContainer *get_toolbar_right() const { return toolbar_right; }
- Label *get_tile_info() const { return tile_info; }
-
- bool forward_gui_input(const Ref<InputEvent> &p_event);
- void forward_canvas_draw_over_viewport(Control *p_overlay);
-
- void edit(Node *p_tile_map);
-
- TileMapEditor(EditorNode *p_editor);
- ~TileMapEditor();
-};
-
-class TileMapEditorPlugin : public EditorPlugin {
- GDCLASS(TileMapEditorPlugin, EditorPlugin);
-
- TileMapEditor *tile_map_editor;
-
-protected:
- void _notification(int p_what);
-
-public:
- virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return tile_map_editor->forward_gui_input(p_event); }
- virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { tile_map_editor->forward_canvas_draw_over_viewport(p_overlay); }
-
- virtual String get_name() const override { return "TileMap"; }
- bool has_main_screen() const override { return false; }
- virtual void edit(Object *p_object) override;
- virtual bool handles(Object *p_object) const override;
- virtual void make_visible(bool p_visible) override;
-
- TileMapEditorPlugin(EditorNode *p_node);
- ~TileMapEditorPlugin();
-};
-
-#endif // TILE_MAP_EDITOR_PLUGIN_H
diff --git a/editor/plugins/tile_set_editor_plugin.cpp b/editor/plugins/tile_set_editor_plugin.cpp
deleted file mode 100644
index f683c4b10d..0000000000
--- a/editor/plugins/tile_set_editor_plugin.cpp
+++ /dev/null
@@ -1,3680 +0,0 @@
-/*************************************************************************/
-/* tile_set_editor_plugin.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_editor_plugin.h"
-
-#include "core/input/input.h"
-#include "core/os/keyboard.h"
-#include "editor/editor_scale.h"
-#include "editor/plugins/canvas_item_editor_plugin.h"
-#include "scene/2d/physics_body_2d.h"
-#include "scene/2d/sprite_2d.h"
-
-void TileSetEditor::edit(const Ref<TileSet> &p_tileset) {
- tileset = p_tileset;
-
- texture_list->clear();
- texture_map.clear();
- update_texture_list();
-}
-
-void TileSetEditor::_import_node(Node *p_node, Ref<TileSet> p_library) {
- for (int i = 0; i < p_node->get_child_count(); i++) {
- Node *child = p_node->get_child(i);
-
- if (!Object::cast_to<Sprite2D>(child)) {
- if (child->get_child_count() > 0) {
- _import_node(child, p_library);
- }
-
- continue;
- }
-
- Sprite2D *mi = Object::cast_to<Sprite2D>(child);
- Ref<Texture2D> texture = mi->get_texture();
- Ref<ShaderMaterial> material = mi->get_material();
-
- if (texture.is_null()) {
- continue;
- }
-
- int id = p_library->find_tile_by_name(mi->get_name());
- if (id < 0) {
- id = p_library->get_last_unused_tile_id();
- p_library->create_tile(id);
- p_library->tile_set_name(id, mi->get_name());
- }
-
- p_library->tile_set_texture(id, texture);
- p_library->tile_set_material(id, material);
-
- p_library->tile_set_modulate(id, mi->get_modulate());
-
- Vector2 phys_offset;
- Size2 s;
-
- if (mi->is_region_enabled()) {
- s = mi->get_region_rect().size;
- p_library->tile_set_region(id, mi->get_region_rect());
- } else {
- const int frame = mi->get_frame();
- const int hframes = mi->get_hframes();
- s = texture->get_size() / Size2(hframes, mi->get_vframes());
- p_library->tile_set_region(id, Rect2(Vector2(frame % hframes, frame / hframes) * s, s));
- }
-
- if (mi->is_centered()) {
- phys_offset += -s / 2;
- }
-
- Vector<TileSet::ShapeData> collisions;
- Ref<NavigationPolygon> nav_poly;
- Ref<OccluderPolygon2D> occluder;
- bool found_collisions = false;
-
- for (int j = 0; j < mi->get_child_count(); j++) {
- Node *child2 = mi->get_child(j);
-
- if (Object::cast_to<NavigationRegion2D>(child2)) {
- nav_poly = Object::cast_to<NavigationRegion2D>(child2)->get_navigation_polygon();
- }
-
- if (Object::cast_to<LightOccluder2D>(child2)) {
- occluder = Object::cast_to<LightOccluder2D>(child2)->get_occluder_polygon();
- }
-
- if (!Object::cast_to<StaticBody2D>(child2)) {
- continue;
- }
-
- found_collisions = true;
-
- StaticBody2D *sb = Object::cast_to<StaticBody2D>(child2);
-
- List<uint32_t> shapes;
- sb->get_shape_owners(&shapes);
-
- for (List<uint32_t>::Element *E = shapes.front(); E; E = E->next()) {
- if (sb->is_shape_owner_disabled(E->get())) {
- continue;
- }
-
- Transform2D shape_transform = sb->get_transform() * sb->shape_owner_get_transform(E->get());
- bool one_way = sb->is_shape_owner_one_way_collision_enabled(E->get());
-
- shape_transform[2] -= phys_offset;
-
- for (int k = 0; k < sb->shape_owner_get_shape_count(E->get()); k++) {
- Ref<Shape2D> shape = sb->shape_owner_get_shape(E->get(), k);
- TileSet::ShapeData shape_data;
- shape_data.shape = shape;
- shape_data.shape_transform = shape_transform;
- shape_data.one_way_collision = one_way;
- collisions.push_back(shape_data);
- }
- }
- }
-
- if (found_collisions) {
- p_library->tile_set_shapes(id, collisions);
- }
-
- p_library->tile_set_texture_offset(id, mi->get_offset());
- p_library->tile_set_navigation_polygon(id, nav_poly);
- p_library->tile_set_light_occluder(id, occluder);
- p_library->tile_set_occluder_offset(id, -phys_offset);
- p_library->tile_set_navigation_polygon_offset(id, -phys_offset);
- p_library->tile_set_z_index(id, mi->get_z_index());
- }
-}
-
-void TileSetEditor::_import_scene(Node *p_scene, Ref<TileSet> p_library, bool p_merge) {
- if (!p_merge) {
- p_library->clear();
- }
-
- _import_node(p_scene, p_library);
-}
-
-void TileSetEditor::_undo_redo_import_scene(Node *p_scene, bool p_merge) {
- _import_scene(p_scene, tileset, p_merge);
-}
-
-Error TileSetEditor::update_library_file(Node *p_base_scene, Ref<TileSet> ml, bool p_merge) {
- _import_scene(p_base_scene, ml, p_merge);
- return OK;
-}
-
-Variant TileSetEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
- return false;
-}
-
-bool TileSetEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
- Dictionary d = p_data;
-
- if (!d.has("type")) {
- return false;
- }
-
- if (d.has("from") && (Object *)(d["from"]) == texture_list) {
- return false;
- }
-
- if (String(d["type"]) == "resource" && d.has("resource")) {
- RES r = d["resource"];
-
- Ref<Texture2D> texture = r;
-
- if (texture.is_valid()) {
- return true;
- }
- }
-
- 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, "Texture")) {
- return false;
- }
- }
-
- return true;
- }
- return false;
-}
-
-void TileSetEditor::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;
- }
-
- Dictionary d = p_data;
-
- if (!d.has("type")) {
- return;
- }
-
- if (String(d["type"]) == "resource" && d.has("resource")) {
- RES r = d["resource"];
-
- Ref<Texture2D> texture = r;
-
- if (texture.is_valid()) {
- add_texture(texture);
- }
-
- if (texture_list->get_item_count() > 0) {
- update_texture_list_icon();
- texture_list->select(texture_list->get_item_count() - 1);
- _on_texture_list_selected(texture_list->get_item_count() - 1);
- }
- }
-
- if (String(d["type"]) == "files") {
- Vector<String> files = d["files"];
-
- _on_textures_added(files);
- }
-}
-
-void TileSetEditor::_bind_methods() {
- ClassDB::bind_method("_undo_redo_import_scene", &TileSetEditor::_undo_redo_import_scene);
- ClassDB::bind_method("_on_workspace_process", &TileSetEditor::_on_workspace_process); // Still used by some connect_compat.
- ClassDB::bind_method("_set_snap_step", &TileSetEditor::_set_snap_step);
- ClassDB::bind_method("_set_snap_off", &TileSetEditor::_set_snap_off);
- ClassDB::bind_method("_set_snap_sep", &TileSetEditor::_set_snap_sep);
- ClassDB::bind_method("_validate_current_tile_id", &TileSetEditor::_validate_current_tile_id);
- ClassDB::bind_method("_select_edited_shape_coord", &TileSetEditor::_select_edited_shape_coord);
- ClassDB::bind_method("_sort_tiles", &TileSetEditor::_sort_tiles);
-
- ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &TileSetEditor::get_drag_data_fw);
- ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &TileSetEditor::can_drop_data_fw);
- ClassDB::bind_method(D_METHOD("drop_data_fw"), &TileSetEditor::drop_data_fw);
-
- ClassDB::bind_method("edit", &TileSetEditor::edit);
- ClassDB::bind_method("add_texture", &TileSetEditor::add_texture);
- ClassDB::bind_method("remove_texture", &TileSetEditor::remove_texture);
- ClassDB::bind_method("update_texture_list_icon", &TileSetEditor::update_texture_list_icon);
- ClassDB::bind_method("update_workspace_minsize", &TileSetEditor::update_workspace_minsize);
-}
-
-void TileSetEditor::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_READY: {
- add_theme_constant_override("autohide", 1); // Fixes the dragger always showing up.
- } break;
- case NOTIFICATION_TRANSLATION_CHANGED:
- case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
- case NOTIFICATION_ENTER_TREE:
- case NOTIFICATION_THEME_CHANGED: {
- tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->set_icon(get_theme_icon("ToolAddNode", "EditorIcons"));
- tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->set_icon(get_theme_icon("Remove", "EditorIcons"));
- tileset_toolbar_tools->set_icon(get_theme_icon("Tools", "EditorIcons"));
-
- tool_workspacemode[WORKSPACE_EDIT]->set_icon(get_theme_icon("Edit", "EditorIcons"));
- tool_workspacemode[WORKSPACE_CREATE_SINGLE]->set_icon(get_theme_icon("AddSingleTile", "EditorIcons"));
- tool_workspacemode[WORKSPACE_CREATE_AUTOTILE]->set_icon(get_theme_icon("AddAutotile", "EditorIcons"));
- tool_workspacemode[WORKSPACE_CREATE_ATLAS]->set_icon(get_theme_icon("AddAtlasTile", "EditorIcons"));
-
- tools[TOOL_SELECT]->set_icon(get_theme_icon("ToolSelect", "EditorIcons"));
- tools[BITMASK_COPY]->set_icon(get_theme_icon("Duplicate", "EditorIcons"));
- tools[BITMASK_PASTE]->set_icon(get_theme_icon("Override", "EditorIcons"));
- tools[BITMASK_CLEAR]->set_icon(get_theme_icon("Clear", "EditorIcons"));
- tools[SHAPE_NEW_POLYGON]->set_icon(get_theme_icon("CollisionPolygon2D", "EditorIcons"));
- tools[SHAPE_NEW_RECTANGLE]->set_icon(get_theme_icon("CollisionShape2D", "EditorIcons"));
- if (is_layout_rtl()) {
- tools[SELECT_PREVIOUS]->set_icon(get_theme_icon("ArrowLeft", "EditorIcons"));
- tools[SELECT_NEXT]->set_icon(get_theme_icon("ArrowRight", "EditorIcons"));
- } else {
- tools[SELECT_PREVIOUS]->set_icon(get_theme_icon("ArrowRight", "EditorIcons"));
- tools[SELECT_NEXT]->set_icon(get_theme_icon("ArrowLeft", "EditorIcons"));
- }
- tools[SHAPE_DELETE]->set_icon(get_theme_icon("Remove", "EditorIcons"));
- tools[SHAPE_KEEP_INSIDE_TILE]->set_icon(get_theme_icon("Snap", "EditorIcons"));
- tools[TOOL_GRID_SNAP]->set_icon(get_theme_icon("SnapGrid", "EditorIcons"));
- tools[ZOOM_OUT]->set_icon(get_theme_icon("ZoomLess", "EditorIcons"));
- tools[ZOOM_1]->set_icon(get_theme_icon("ZoomReset", "EditorIcons"));
- tools[ZOOM_IN]->set_icon(get_theme_icon("ZoomMore", "EditorIcons"));
- tools[VISIBLE_INFO]->set_icon(get_theme_icon("InformationSign", "EditorIcons"));
- _update_toggle_shape_button();
-
- tool_editmode[EDITMODE_REGION]->set_icon(get_theme_icon("RegionEdit", "EditorIcons"));
- tool_editmode[EDITMODE_COLLISION]->set_icon(get_theme_icon("StaticBody2D", "EditorIcons"));
- tool_editmode[EDITMODE_OCCLUSION]->set_icon(get_theme_icon("LightOccluder2D", "EditorIcons"));
- tool_editmode[EDITMODE_NAVIGATION]->set_icon(get_theme_icon("Navigation2D", "EditorIcons"));
- tool_editmode[EDITMODE_BITMASK]->set_icon(get_theme_icon("PackedDataContainer", "EditorIcons"));
- tool_editmode[EDITMODE_PRIORITY]->set_icon(get_theme_icon("MaterialPreviewLight1", "EditorIcons"));
- tool_editmode[EDITMODE_ICON]->set_icon(get_theme_icon("Image", "EditorIcons"));
- tool_editmode[EDITMODE_Z_INDEX]->set_icon(get_theme_icon("Sort", "EditorIcons"));
-
- scroll->add_theme_style_override("bg", get_theme_stylebox("bg", "Tree"));
- } break;
- }
-}
-
-TileSetEditor::TileSetEditor(EditorNode *p_editor) {
- editor = p_editor;
- undo_redo = EditorNode::get_undo_redo();
- current_tile = -1;
-
- VBoxContainer *left_container = memnew(VBoxContainer);
- add_child(left_container);
-
- texture_list = memnew(ItemList);
- left_container->add_child(texture_list);
- texture_list->set_v_size_flags(SIZE_EXPAND_FILL);
- texture_list->set_custom_minimum_size(Size2(200, 0));
- texture_list->connect("item_selected", callable_mp(this, &TileSetEditor::_on_texture_list_selected));
- texture_list->set_drag_forwarding(this);
-
- HBoxContainer *tileset_toolbar_container = memnew(HBoxContainer);
- left_container->add_child(tileset_toolbar_container);
-
- tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE] = memnew(Button);
- tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->set_flat(true);
- tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tileset_toolbar_button_pressed), varray(TOOL_TILESET_ADD_TEXTURE));
- tileset_toolbar_container->add_child(tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]);
- tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->set_tooltip(TTR("Add Texture(s) to TileSet."));
-
- tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE] = memnew(Button);
- tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->set_flat(true);
- tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tileset_toolbar_button_pressed), varray(TOOL_TILESET_REMOVE_TEXTURE));
- tileset_toolbar_container->add_child(tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]);
- tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->set_tooltip(TTR("Remove selected Texture from TileSet."));
-
- Control *toolbar_separator = memnew(Control);
- toolbar_separator->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- tileset_toolbar_container->add_child(toolbar_separator);
-
- tileset_toolbar_tools = memnew(MenuButton);
- tileset_toolbar_tools->set_text(TTR("Tools"));
- tileset_toolbar_tools->get_popup()->add_item(TTR("Create from Scene"), TOOL_TILESET_CREATE_SCENE);
- tileset_toolbar_tools->get_popup()->add_item(TTR("Merge from Scene"), TOOL_TILESET_MERGE_SCENE);
-
- tileset_toolbar_tools->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_on_tileset_toolbar_button_pressed));
- tileset_toolbar_container->add_child(tileset_toolbar_tools);
-
- //---------------
- VBoxContainer *right_container = memnew(VBoxContainer);
- right_container->set_v_size_flags(SIZE_EXPAND_FILL);
- add_child(right_container);
-
- dragging_point = -1;
- creating_shape = false;
- snap_step = Vector2(32, 32);
- snap_offset = WORKSPACE_MARGIN;
-
- set_custom_minimum_size(Size2(0, 150));
-
- VBoxContainer *main_vb = memnew(VBoxContainer);
- right_container->add_child(main_vb);
- main_vb->set_v_size_flags(SIZE_EXPAND_FILL);
-
- HBoxContainer *tool_hb = memnew(HBoxContainer);
- Ref<ButtonGroup> g(memnew(ButtonGroup));
-
- String workspace_label[WORKSPACE_MODE_MAX] = {
- TTR("Edit"),
- TTR("New Single Tile"),
- TTR("New Autotile"),
- TTR("New Atlas")
- };
- for (int i = 0; i < (int)WORKSPACE_MODE_MAX; i++) {
- tool_workspacemode[i] = memnew(Button);
- tool_workspacemode[i]->set_text(workspace_label[i]);
- tool_workspacemode[i]->set_toggle_mode(true);
- tool_workspacemode[i]->set_button_group(g);
- tool_workspacemode[i]->connect("pressed", callable_mp(this, &TileSetEditor::_on_workspace_mode_changed), varray(i));
- tool_hb->add_child(tool_workspacemode[i]);
- }
-
- Control *spacer = memnew(Control);
- spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- tool_hb->add_child(spacer);
- tool_hb->move_child(spacer, WORKSPACE_CREATE_SINGLE);
-
- tools[SELECT_NEXT] = memnew(Button);
- tool_hb->add_child(tools[SELECT_NEXT]);
- tool_hb->move_child(tools[SELECT_NEXT], WORKSPACE_CREATE_SINGLE);
- tools[SELECT_NEXT]->set_flat(true);
- tools[SELECT_NEXT]->set_shortcut(ED_SHORTCUT("tileset_editor/next_shape", TTR("Next Coordinate"), KEY_PAGEDOWN));
- tools[SELECT_NEXT]->set_shortcut_context(this);
- tools[SELECT_NEXT]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SELECT_NEXT));
- tools[SELECT_NEXT]->set_tooltip(TTR("Select the next shape, subtile, or Tile."));
- tools[SELECT_PREVIOUS] = memnew(Button);
- tool_hb->add_child(tools[SELECT_PREVIOUS]);
- tool_hb->move_child(tools[SELECT_PREVIOUS], WORKSPACE_CREATE_SINGLE);
- tools[SELECT_PREVIOUS]->set_flat(true);
- tools[SELECT_PREVIOUS]->set_shortcut(ED_SHORTCUT("tileset_editor/previous_shape", TTR("Previous Coordinate"), KEY_PAGEUP));
- tools[SELECT_PREVIOUS]->set_shortcut_context(this);
- tools[SELECT_PREVIOUS]->set_tooltip(TTR("Select the previous shape, subtile, or Tile."));
- tools[SELECT_PREVIOUS]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SELECT_PREVIOUS));
-
- VSeparator *separator_shape_selection = memnew(VSeparator);
- tool_hb->add_child(separator_shape_selection);
- tool_hb->move_child(separator_shape_selection, WORKSPACE_CREATE_SINGLE);
-
- tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true);
- workspace_mode = WORKSPACE_EDIT;
-
- main_vb->add_child(tool_hb);
- main_vb->add_child(memnew(HSeparator));
-
- tool_hb = memnew(HBoxContainer);
-
- g = Ref<ButtonGroup>(memnew(ButtonGroup));
- String label[EDITMODE_MAX] = {
- TTR("Region"),
- TTR("Collision"),
- TTR("Occlusion"),
- TTR("Navigation"),
- TTR("Bitmask"),
- TTR("Priority"),
- TTR("Icon"),
- TTR("Z Index")
- };
- for (int i = 0; i < (int)EDITMODE_MAX; i++) {
- tool_editmode[i] = memnew(Button);
- tool_editmode[i]->set_text(label[i]);
- tool_editmode[i]->set_toggle_mode(true);
- tool_editmode[i]->set_button_group(g);
- tool_editmode[i]->connect("pressed", callable_mp(this, &TileSetEditor::_on_edit_mode_changed), varray(i));
- tool_hb->add_child(tool_editmode[i]);
- }
- tool_editmode[EDITMODE_COLLISION]->set_pressed(true);
- edit_mode = EDITMODE_COLLISION;
-
- tool_editmode[EDITMODE_REGION]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_region", TTR("Region Mode"), KEY_1));
- tool_editmode[EDITMODE_COLLISION]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_collision", TTR("Collision Mode"), KEY_2));
- tool_editmode[EDITMODE_OCCLUSION]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_occlusion", TTR("Occlusion Mode"), KEY_3));
- tool_editmode[EDITMODE_NAVIGATION]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_navigation", TTR("Navigation Mode"), KEY_4));
- tool_editmode[EDITMODE_BITMASK]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_bitmask", TTR("Bitmask Mode"), KEY_5));
- tool_editmode[EDITMODE_PRIORITY]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_priority", TTR("Priority Mode"), KEY_6));
- tool_editmode[EDITMODE_ICON]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_icon", TTR("Icon Mode"), KEY_7));
- tool_editmode[EDITMODE_Z_INDEX]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_z_index", TTR("Z Index Mode"), KEY_8));
-
- tool_editmode[EDITMODE_REGION]->set_shortcut_context(this);
- tool_editmode[EDITMODE_REGION]->set_shortcut_context(this);
- tool_editmode[EDITMODE_COLLISION]->set_shortcut_context(this);
- tool_editmode[EDITMODE_OCCLUSION]->set_shortcut_context(this);
- tool_editmode[EDITMODE_NAVIGATION]->set_shortcut_context(this);
- tool_editmode[EDITMODE_BITMASK]->set_shortcut_context(this);
- tool_editmode[EDITMODE_PRIORITY]->set_shortcut_context(this);
- tool_editmode[EDITMODE_ICON]->set_shortcut_context(this);
- tool_editmode[EDITMODE_Z_INDEX]->set_shortcut_context(this);
-
- main_vb->add_child(tool_hb);
- separator_editmode = memnew(HSeparator);
- main_vb->add_child(separator_editmode);
-
- toolbar = memnew(HBoxContainer);
- Ref<ButtonGroup> tg(memnew(ButtonGroup));
-
- tools[TOOL_SELECT] = memnew(Button);
- toolbar->add_child(tools[TOOL_SELECT]);
- tools[TOOL_SELECT]->set_flat(true);
- tools[TOOL_SELECT]->set_toggle_mode(true);
- tools[TOOL_SELECT]->set_button_group(tg);
- tools[TOOL_SELECT]->set_pressed(true);
- tools[TOOL_SELECT]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(TOOL_SELECT));
-
- separator_bitmask = memnew(VSeparator);
- toolbar->add_child(separator_bitmask);
- tools[BITMASK_COPY] = memnew(Button);
- tools[BITMASK_COPY]->set_flat(true);
- tools[BITMASK_COPY]->set_tooltip(TTR("Copy bitmask."));
- tools[BITMASK_COPY]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(BITMASK_COPY));
- toolbar->add_child(tools[BITMASK_COPY]);
- tools[BITMASK_PASTE] = memnew(Button);
- tools[BITMASK_PASTE]->set_flat(true);
- tools[BITMASK_PASTE]->set_tooltip(TTR("Paste bitmask."));
- tools[BITMASK_PASTE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(BITMASK_PASTE));
- toolbar->add_child(tools[BITMASK_PASTE]);
- tools[BITMASK_CLEAR] = memnew(Button);
- tools[BITMASK_CLEAR]->set_flat(true);
- tools[BITMASK_CLEAR]->set_tooltip(TTR("Erase bitmask."));
- tools[BITMASK_CLEAR]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(BITMASK_CLEAR));
- toolbar->add_child(tools[BITMASK_CLEAR]);
-
- tools[SHAPE_NEW_RECTANGLE] = memnew(Button);
- toolbar->add_child(tools[SHAPE_NEW_RECTANGLE]);
- tools[SHAPE_NEW_RECTANGLE]->set_flat(true);
- tools[SHAPE_NEW_RECTANGLE]->set_toggle_mode(true);
- tools[SHAPE_NEW_RECTANGLE]->set_button_group(tg);
- tools[SHAPE_NEW_RECTANGLE]->set_tooltip(TTR("Create a new rectangle."));
- tools[SHAPE_NEW_RECTANGLE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SHAPE_NEW_RECTANGLE));
- tools[SHAPE_NEW_RECTANGLE]->set_shortcut(ED_SHORTCUT("tileset_editor/shape_new_rectangle", TTR("New Rectangle"), KEY_MASK_SHIFT | KEY_R));
-
- tools[SHAPE_NEW_POLYGON] = memnew(Button);
- toolbar->add_child(tools[SHAPE_NEW_POLYGON]);
- tools[SHAPE_NEW_POLYGON]->set_flat(true);
- tools[SHAPE_NEW_POLYGON]->set_toggle_mode(true);
- tools[SHAPE_NEW_POLYGON]->set_button_group(tg);
- tools[SHAPE_NEW_POLYGON]->set_tooltip(TTR("Create a new polygon."));
- tools[SHAPE_NEW_POLYGON]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SHAPE_NEW_POLYGON));
- tools[SHAPE_NEW_POLYGON]->set_shortcut(ED_SHORTCUT("tileset_editor/shape_new_polygon", TTR("New Polygon"), KEY_MASK_SHIFT | KEY_P));
-
- separator_shape_toggle = memnew(VSeparator);
- toolbar->add_child(separator_shape_toggle);
- tools[SHAPE_TOGGLE_TYPE] = memnew(Button);
- tools[SHAPE_TOGGLE_TYPE]->set_flat(true);
- tools[SHAPE_TOGGLE_TYPE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SHAPE_TOGGLE_TYPE));
- toolbar->add_child(tools[SHAPE_TOGGLE_TYPE]);
-
- separator_delete = memnew(VSeparator);
- toolbar->add_child(separator_delete);
- tools[SHAPE_DELETE] = memnew(Button);
- tools[SHAPE_DELETE]->set_flat(true);
- tools[SHAPE_DELETE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SHAPE_DELETE));
- tools[SHAPE_DELETE]->set_shortcut(ED_SHORTCUT("tileset_editor/shape_delete", TTR("Delete Selected Shape"), KEY_MASK_SHIFT | KEY_BACKSPACE));
- toolbar->add_child(tools[SHAPE_DELETE]);
-
- spin_priority = memnew(SpinBox);
- spin_priority->set_min(1);
- spin_priority->set_max(255);
- spin_priority->set_step(1);
- spin_priority->set_custom_minimum_size(Size2(100, 0));
- spin_priority->connect("value_changed", callable_mp(this, &TileSetEditor::_on_priority_changed));
- spin_priority->hide();
- toolbar->add_child(spin_priority);
-
- spin_z_index = memnew(SpinBox);
- spin_z_index->set_min(RS::CANVAS_ITEM_Z_MIN);
- spin_z_index->set_max(RS::CANVAS_ITEM_Z_MAX);
- spin_z_index->set_step(1);
- spin_z_index->set_custom_minimum_size(Size2(100, 0));
- spin_z_index->connect("value_changed", callable_mp(this, &TileSetEditor::_on_z_index_changed));
- spin_z_index->hide();
- toolbar->add_child(spin_z_index);
-
- separator_grid = memnew(VSeparator);
- toolbar->add_child(separator_grid);
- tools[SHAPE_KEEP_INSIDE_TILE] = memnew(Button);
- tools[SHAPE_KEEP_INSIDE_TILE]->set_flat(true);
- tools[SHAPE_KEEP_INSIDE_TILE]->set_toggle_mode(true);
- tools[SHAPE_KEEP_INSIDE_TILE]->set_pressed(true);
- tools[SHAPE_KEEP_INSIDE_TILE]->set_tooltip(TTR("Keep polygon inside region Rect."));
- toolbar->add_child(tools[SHAPE_KEEP_INSIDE_TILE]);
- tools[TOOL_GRID_SNAP] = memnew(Button);
- tools[TOOL_GRID_SNAP]->set_flat(true);
- tools[TOOL_GRID_SNAP]->set_toggle_mode(true);
- tools[TOOL_GRID_SNAP]->set_tooltip(TTR("Enable snap and show grid (configurable via the Inspector)."));
- tools[TOOL_GRID_SNAP]->connect("toggled", callable_mp(this, &TileSetEditor::_on_grid_snap_toggled));
- toolbar->add_child(tools[TOOL_GRID_SNAP]);
-
- Control *separator = memnew(Control);
- separator->set_h_size_flags(SIZE_EXPAND_FILL);
- toolbar->add_child(separator);
-
- tools[ZOOM_OUT] = memnew(Button);
- tools[ZOOM_OUT]->set_flat(true);
- tools[ZOOM_OUT]->connect("pressed", callable_mp(this, &TileSetEditor::_zoom_out));
- toolbar->add_child(tools[ZOOM_OUT]);
- tools[ZOOM_OUT]->set_tooltip(TTR("Zoom Out"));
- tools[ZOOM_1] = memnew(Button);
- tools[ZOOM_1]->set_flat(true);
- tools[ZOOM_1]->connect("pressed", callable_mp(this, &TileSetEditor::_zoom_reset));
- toolbar->add_child(tools[ZOOM_1]);
- tools[ZOOM_1]->set_tooltip(TTR("Zoom Reset"));
- tools[ZOOM_IN] = memnew(Button);
- tools[ZOOM_IN]->set_flat(true);
- tools[ZOOM_IN]->connect("pressed", callable_mp(this, &TileSetEditor::_zoom_in));
- toolbar->add_child(tools[ZOOM_IN]);
- tools[ZOOM_IN]->set_tooltip(TTR("Zoom In"));
-
- tools[VISIBLE_INFO] = memnew(Button);
- tools[VISIBLE_INFO]->set_flat(true);
- tools[VISIBLE_INFO]->set_toggle_mode(true);
- tools[VISIBLE_INFO]->set_tooltip(TTR("Display Tile Names (Hold Alt Key)"));
- toolbar->add_child(tools[VISIBLE_INFO]);
-
- main_vb->add_child(toolbar);
-
- scroll = memnew(ScrollContainer);
- main_vb->add_child(scroll);
- scroll->set_v_size_flags(SIZE_EXPAND_FILL);
- scroll->connect("gui_input", callable_mp(this, &TileSetEditor::_on_scroll_container_input));
- scroll->set_clip_contents(true);
-
- empty_message = memnew(Label);
- empty_message->set_text(TTR("Add or select a texture on the left panel to edit the tiles bound to it."));
- empty_message->set_valign(Label::VALIGN_CENTER);
- empty_message->set_align(Label::ALIGN_CENTER);
- empty_message->set_autowrap(true);
- empty_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
- empty_message->set_v_size_flags(SIZE_EXPAND_FILL);
- main_vb->add_child(empty_message);
-
- workspace_container = memnew(Control);
- scroll->add_child(workspace_container);
-
- workspace_overlay = memnew(Control);
- workspace_overlay->connect("draw", callable_mp(this, &TileSetEditor::_on_workspace_overlay_draw));
- workspace_container->add_child(workspace_overlay);
-
- workspace = memnew(Control);
- workspace->set_focus_mode(FOCUS_ALL);
- workspace->connect("draw", callable_mp(this, &TileSetEditor::_on_workspace_draw));
- workspace->connect("gui_input", callable_mp(this, &TileSetEditor::_on_workspace_input));
- workspace->set_draw_behind_parent(true);
- workspace_overlay->add_child(workspace);
-
- preview = memnew(Sprite2D);
- workspace->add_child(preview);
- preview->set_centered(false);
- preview->set_draw_behind_parent(true);
- preview->set_position(WORKSPACE_MARGIN);
-
- //---------------
- cd = memnew(ConfirmationDialog);
- add_child(cd);
- cd->connect("confirmed", callable_mp(this, &TileSetEditor::_on_tileset_toolbar_confirm));
-
- //---------------
- err_dialog = memnew(AcceptDialog);
- add_child(err_dialog);
-
- //---------------
- texture_dialog = memnew(EditorFileDialog);
- texture_dialog->set_access(EditorFileDialog::ACCESS_RESOURCES);
- texture_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);
- texture_dialog->clear_filters();
- List<String> extensions;
-
- ResourceLoader::get_recognized_extensions_for_type("Texture2D", &extensions);
- for (List<String>::Element *E = extensions.front(); E; E = E->next()) {
- texture_dialog->add_filter("*." + E->get() + " ; " + E->get().to_upper());
- }
- add_child(texture_dialog);
- texture_dialog->connect("files_selected", callable_mp(this, &TileSetEditor::_on_textures_added));
-
- //---------------
- helper = memnew(TilesetEditorContext(this));
- tile_names_visible = false;
-
- // Config scale.
- max_scale = 16.0f;
- min_scale = 0.01f;
- scale_ratio = 1.2f;
-}
-
-TileSetEditor::~TileSetEditor() {
- if (helper) {
- memdelete(helper);
- }
-}
-
-void TileSetEditor::_on_tileset_toolbar_button_pressed(int p_index) {
- option = p_index;
- switch (option) {
- case TOOL_TILESET_ADD_TEXTURE: {
- texture_dialog->popup_file_dialog();
- } break;
- case TOOL_TILESET_REMOVE_TEXTURE: {
- if (get_current_texture().is_valid()) {
- cd->set_text(TTR("Remove selected texture? This will remove all tiles which use it."));
- cd->popup_centered(Size2(300, 60));
- } else {
- err_dialog->set_text(TTR("You haven't selected a texture to remove."));
- err_dialog->popup_centered(Size2(300, 60));
- }
- } break;
- case TOOL_TILESET_CREATE_SCENE: {
- cd->set_text(TTR("Create from scene? This will overwrite all current tiles."));
- cd->popup_centered(Size2(300, 60));
- } break;
- case TOOL_TILESET_MERGE_SCENE: {
- cd->set_text(TTR("Merge from scene?"));
- cd->popup_centered(Size2(300, 60));
- } break;
- }
-}
-
-void TileSetEditor::_on_tileset_toolbar_confirm() {
- switch (option) {
- case TOOL_TILESET_REMOVE_TEXTURE: {
- RID current_rid = get_current_texture()->get_rid();
- List<int> ids;
- tileset->get_tile_list(&ids);
-
- undo_redo->create_action(TTR("Remove Texture"));
- for (List<int>::Element *E = ids.front(); E; E = E->next()) {
- if (tileset->tile_get_texture(E->get())->get_rid() == current_rid) {
- undo_redo->add_do_method(tileset.ptr(), "remove_tile", E->get());
- _undo_tile_removal(E->get());
- }
- }
- undo_redo->add_do_method(this, "remove_texture", get_current_texture());
- undo_redo->add_undo_method(this, "add_texture", get_current_texture());
- undo_redo->add_undo_method(this, "update_texture_list_icon");
- undo_redo->commit_action();
- } break;
- case TOOL_TILESET_MERGE_SCENE:
- case TOOL_TILESET_CREATE_SCENE: {
- EditorNode *en = editor;
- Node *scene = en->get_edited_scene();
- if (!scene) {
- break;
- }
-
- List<int> ids;
- tileset->get_tile_list(&ids);
-
- undo_redo->create_action(option == TOOL_TILESET_MERGE_SCENE ? TTR("Merge Tileset from Scene") : TTR("Create Tileset from Scene"));
- undo_redo->add_do_method(this, "_undo_redo_import_scene", scene, option == TOOL_TILESET_MERGE_SCENE);
- undo_redo->add_undo_method(tileset.ptr(), "clear");
- for (List<int>::Element *E = ids.front(); E; E = E->next()) {
- _undo_tile_removal(E->get());
- }
- undo_redo->add_do_method(this, "edit", tileset);
- undo_redo->add_undo_method(this, "edit", tileset);
- undo_redo->commit_action();
- } break;
- }
-}
-
-void TileSetEditor::_on_texture_list_selected(int p_index) {
- if (get_current_texture().is_valid()) {
- current_item_index = p_index;
- preview->set_texture(get_current_texture());
- update_workspace_tile_mode();
- update_workspace_minsize();
- } else {
- current_item_index = -1;
- preview->set_texture(nullptr);
- workspace->set_custom_minimum_size(Size2i());
- update_workspace_tile_mode();
- }
-
- set_current_tile(-1);
- workspace->update();
-}
-
-void TileSetEditor::_on_textures_added(const PackedStringArray &p_paths) {
- int invalid_count = 0;
- for (int i = 0; i < p_paths.size(); i++) {
- Ref<Texture2D> t = Ref<Texture2D>(ResourceLoader::load(p_paths[i]));
-
- ERR_CONTINUE_MSG(!t.is_valid(), "'" + p_paths[i] + "' is not a valid texture.");
-
- if (texture_map.has(t->get_rid())) {
- invalid_count++;
- } else {
- add_texture(t);
- }
- }
-
- if (texture_list->get_item_count() > 0) {
- update_texture_list_icon();
- texture_list->select(texture_list->get_item_count() - 1);
- _on_texture_list_selected(texture_list->get_item_count() - 1);
- }
-
- if (invalid_count > 0) {
- err_dialog->set_text(vformat(TTR("%s file(s) were not added because was already on the list."), String::num(invalid_count, 0)));
- err_dialog->popup_centered(Size2(300, 60));
- }
-}
-
-void TileSetEditor::_on_edit_mode_changed(int p_edit_mode) {
- draw_handles = false;
- creating_shape = false;
- edit_mode = (EditMode)p_edit_mode;
- switch (edit_mode) {
- case EDITMODE_REGION: {
- tools[TOOL_SELECT]->show();
-
- separator_bitmask->hide();
- tools[BITMASK_COPY]->hide();
- tools[BITMASK_PASTE]->hide();
- tools[BITMASK_CLEAR]->hide();
- tools[SHAPE_NEW_POLYGON]->hide();
- tools[SHAPE_NEW_RECTANGLE]->hide();
-
- if (workspace_mode == WORKSPACE_EDIT) {
- separator_delete->show();
- tools[SHAPE_DELETE]->show();
- } else {
- separator_delete->hide();
- tools[SHAPE_DELETE]->hide();
- }
-
- separator_grid->show();
- tools[SHAPE_KEEP_INSIDE_TILE]->hide();
- tools[TOOL_GRID_SNAP]->show();
-
- tools[TOOL_SELECT]->set_pressed(true);
- tools[TOOL_SELECT]->set_tooltip(TTR("Drag handles to edit Rect.\nClick on another Tile to edit it."));
- tools[SHAPE_DELETE]->set_tooltip(TTR("Delete selected Rect."));
- spin_priority->hide();
- spin_z_index->hide();
- } break;
- case EDITMODE_COLLISION:
- case EDITMODE_OCCLUSION:
- case EDITMODE_NAVIGATION: {
- tools[TOOL_SELECT]->show();
-
- separator_bitmask->hide();
- tools[BITMASK_COPY]->hide();
- tools[BITMASK_PASTE]->hide();
- tools[BITMASK_CLEAR]->hide();
- tools[SHAPE_NEW_POLYGON]->show();
- tools[SHAPE_NEW_RECTANGLE]->show();
-
- separator_delete->show();
- tools[SHAPE_DELETE]->show();
-
- separator_grid->show();
- tools[SHAPE_KEEP_INSIDE_TILE]->show();
- tools[TOOL_GRID_SNAP]->show();
-
- tools[TOOL_SELECT]->set_tooltip(TTR("Select current edited sub-tile.\nClick on another Tile to edit it."));
- tools[SHAPE_DELETE]->set_tooltip(TTR("Delete polygon."));
- spin_priority->hide();
- spin_z_index->hide();
-
- _select_edited_shape_coord();
- } break;
- case EDITMODE_BITMASK: {
- tools[TOOL_SELECT]->show();
-
- separator_bitmask->show();
- tools[BITMASK_COPY]->show();
- tools[BITMASK_PASTE]->show();
- tools[BITMASK_CLEAR]->show();
- tools[SHAPE_NEW_POLYGON]->hide();
- tools[SHAPE_NEW_RECTANGLE]->hide();
-
- separator_delete->hide();
- tools[SHAPE_DELETE]->hide();
-
- tools[SHAPE_KEEP_INSIDE_TILE]->hide();
-
- tools[TOOL_SELECT]->set_pressed(true);
- tools[TOOL_SELECT]->set_tooltip(TTR("LMB: Set bit on.\nRMB: Set bit off.\nShift+LMB: Set wildcard bit.\nClick on another Tile to edit it."));
- spin_priority->hide();
- } break;
- case EDITMODE_Z_INDEX:
- case EDITMODE_PRIORITY:
- case EDITMODE_ICON: {
- tools[TOOL_SELECT]->show();
-
- separator_bitmask->hide();
- tools[BITMASK_COPY]->hide();
- tools[BITMASK_PASTE]->hide();
- tools[BITMASK_CLEAR]->hide();
- tools[SHAPE_NEW_POLYGON]->hide();
- tools[SHAPE_NEW_RECTANGLE]->hide();
-
- separator_delete->hide();
- tools[SHAPE_DELETE]->hide();
-
- separator_grid->show();
- tools[SHAPE_KEEP_INSIDE_TILE]->hide();
- tools[TOOL_GRID_SNAP]->show();
-
- if (edit_mode == EDITMODE_ICON) {
- tools[TOOL_SELECT]->set_tooltip(TTR("Select sub-tile to use as icon, this will be also used on invalid autotile bindings.\nClick on another Tile to edit it."));
- spin_priority->hide();
- spin_z_index->hide();
- } else if (edit_mode == EDITMODE_PRIORITY) {
- tools[TOOL_SELECT]->set_tooltip(TTR("Select sub-tile to change its priority.\nClick on another Tile to edit it."));
- spin_priority->show();
- spin_z_index->hide();
- } else {
- tools[TOOL_SELECT]->set_tooltip(TTR("Select sub-tile to change its z index.\nClick on another Tile to edit it."));
- spin_priority->hide();
- spin_z_index->show();
- }
- } break;
- default: {
- }
- }
- _update_toggle_shape_button();
- workspace->update();
-}
-
-void TileSetEditor::_on_workspace_mode_changed(int p_workspace_mode) {
- workspace_mode = (WorkspaceMode)p_workspace_mode;
- if (p_workspace_mode == WORKSPACE_EDIT) {
- update_workspace_tile_mode();
- } else {
- for (int i = 0; i < EDITMODE_MAX; i++) {
- tool_editmode[i]->hide();
- }
- tool_editmode[EDITMODE_REGION]->show();
- tool_editmode[EDITMODE_REGION]->set_pressed(true);
- _on_edit_mode_changed(EDITMODE_REGION);
- separator_editmode->show();
- }
-}
-
-void TileSetEditor::_on_workspace_draw() {
- if (tileset.is_null() || !get_current_texture().is_valid()) {
- return;
- }
-
- const Color COLOR_AUTOTILE = Color(0.3, 0.6, 1);
- const Color COLOR_SINGLE = Color(1, 1, 0.3);
- const Color COLOR_ATLAS = Color(0.8, 0.8, 0.8);
- const Color COLOR_SUBDIVISION = Color(0.3, 0.7, 0.6);
-
- draw_handles = false;
-
- draw_highlight_current_tile();
-
- draw_grid_snap();
- if (get_current_tile() >= 0) {
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->autotile_get_size(get_current_tile());
- Rect2i region = tileset->tile_get_region(get_current_tile());
-
- switch (edit_mode) {
- case EDITMODE_ICON: {
- Vector2 coord = tileset->autotile_get_icon_coordinate(get_current_tile());
- draw_highlight_subtile(coord);
- } break;
- case EDITMODE_BITMASK: {
- Color c(1, 0, 0, 0.5);
- Color ci(0.3, 0.6, 1, 0.5);
- for (int x = 0; x < region.size.x / (spacing + size.x); x++) {
- for (int y = 0; y < region.size.y / (spacing + size.y); y++) {
- Vector2 coord(x, y);
- Point2 anchor(coord.x * (spacing + size.x), coord.y * (spacing + size.y));
- anchor += WORKSPACE_MARGIN;
- anchor += region.position;
- uint32_t mask = tileset->autotile_get_bitmask(get_current_tile(), coord);
- if (tileset->autotile_get_bitmask_mode(get_current_tile()) == TileSet::BITMASK_2X2) {
- if (mask & TileSet::BIND_IGNORE_TOPLEFT) {
- workspace->draw_rect(Rect2(anchor, size / 4), ci);
- workspace->draw_rect(Rect2(anchor + size / 4, size / 4), ci);
- } else if (mask & TileSet::BIND_TOPLEFT) {
- workspace->draw_rect(Rect2(anchor, size / 2), c);
- }
- if (mask & TileSet::BIND_IGNORE_TOPRIGHT) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 2, 0), size / 4), ci);
- workspace->draw_rect(Rect2(anchor + Vector2(size.x * 3 / 4, size.y / 4), size / 4), ci);
- } else if (mask & TileSet::BIND_TOPRIGHT) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 2, 0), size / 2), c);
- }
- if (mask & TileSet::BIND_IGNORE_BOTTOMLEFT) {
- workspace->draw_rect(Rect2(anchor + Vector2(0, size.y / 2), size / 4), ci);
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 4, size.y * 3 / 4), size / 4), ci);
- } else if (mask & TileSet::BIND_BOTTOMLEFT) {
- workspace->draw_rect(Rect2(anchor + Vector2(0, size.y / 2), size / 2), c);
- }
- if (mask & TileSet::BIND_IGNORE_BOTTOMRIGHT) {
- workspace->draw_rect(Rect2(anchor + size / 2, size / 4), ci);
- workspace->draw_rect(Rect2(anchor + size * 3 / 4, size / 4), ci);
- } else if (mask & TileSet::BIND_BOTTOMRIGHT) {
- workspace->draw_rect(Rect2(anchor + size / 2, size / 2), c);
- }
- } else {
- if (mask & TileSet::BIND_IGNORE_TOPLEFT) {
- workspace->draw_rect(Rect2(anchor, size / 6), ci);
- workspace->draw_rect(Rect2(anchor + size / 6, size / 6), ci);
- } else if (mask & TileSet::BIND_TOPLEFT) {
- workspace->draw_rect(Rect2(anchor, size / 3), c);
- }
- if (mask & TileSet::BIND_IGNORE_TOP) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, 0), size / 6), ci);
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 2, size.y / 6), size / 6), ci);
- } else if (mask & TileSet::BIND_TOP) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, 0), size / 3), c);
- }
- if (mask & TileSet::BIND_IGNORE_TOPRIGHT) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x * 4 / 6, 0), size / 6), ci);
- workspace->draw_rect(Rect2(anchor + Vector2(size.x * 5 / 6, size.y / 6), size / 6), ci);
- } else if (mask & TileSet::BIND_TOPRIGHT) {
- workspace->draw_rect(Rect2(anchor + Vector2((size.x / 3) * 2, 0), size / 3), c);
- }
- if (mask & TileSet::BIND_IGNORE_LEFT) {
- workspace->draw_rect(Rect2(anchor + Vector2(0, size.y / 3), size / 6), ci);
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 6, size.y / 2), size / 6), ci);
- } else if (mask & TileSet::BIND_LEFT) {
- workspace->draw_rect(Rect2(anchor + Vector2(0, size.y / 3), size / 3), c);
- }
- if (mask & TileSet::BIND_IGNORE_CENTER) {
- workspace->draw_rect(Rect2(anchor + size / 3, size / 6), ci);
- workspace->draw_rect(Rect2(anchor + size / 2, size / 6), ci);
- } else if (mask & TileSet::BIND_CENTER) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, size.y / 3), size / 3), c);
- }
- if (mask & TileSet::BIND_IGNORE_RIGHT) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x * 4 / 6, size.y / 3), size / 6), ci);
- workspace->draw_rect(Rect2(anchor + Vector2(size.x * 5 / 6, size.y / 2), size / 6), ci);
- } else if (mask & TileSet::BIND_RIGHT) {
- workspace->draw_rect(Rect2(anchor + Vector2((size.x / 3) * 2, size.y / 3), size / 3), c);
- }
- if (mask & TileSet::BIND_IGNORE_BOTTOMLEFT) {
- workspace->draw_rect(Rect2(anchor + Vector2(0, size.y * 4 / 6), size / 6), ci);
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 6, size.y * 5 / 6), size / 6), ci);
- } else if (mask & TileSet::BIND_BOTTOMLEFT) {
- workspace->draw_rect(Rect2(anchor + Vector2(0, (size.y / 3) * 2), size / 3), c);
- }
- if (mask & TileSet::BIND_IGNORE_BOTTOM) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, size.y * 4 / 6), size / 6), ci);
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 2, size.y * 5 / 6), size / 6), ci);
- } else if (mask & TileSet::BIND_BOTTOM) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, (size.y / 3) * 2), size / 3), c);
- }
- if (mask & TileSet::BIND_IGNORE_BOTTOMRIGHT) {
- workspace->draw_rect(Rect2(anchor + size * 4 / 6, size / 6), ci);
- workspace->draw_rect(Rect2(anchor + size * 5 / 6, size / 6), ci);
- } else if (mask & TileSet::BIND_BOTTOMRIGHT) {
- workspace->draw_rect(Rect2(anchor + (size / 3) * 2, size / 3), c);
- }
- }
- }
- }
- } break;
- case EDITMODE_COLLISION:
- case EDITMODE_OCCLUSION:
- case EDITMODE_NAVIGATION: {
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
- draw_highlight_subtile(edited_shape_coord);
- }
- draw_polygon_shapes();
- draw_grid_snap();
- } break;
- case EDITMODE_PRIORITY: {
- spin_priority->set_value(tileset->autotile_get_subtile_priority(get_current_tile(), edited_shape_coord));
- uint32_t mask = tileset->autotile_get_bitmask(get_current_tile(), edited_shape_coord);
- Vector<Vector2> queue_others;
- int total = 0;
- for (Map<Vector2, uint32_t>::Element *E = tileset->autotile_get_bitmask_map(get_current_tile()).front(); E; E = E->next()) {
- if (E->value() == mask) {
- total += tileset->autotile_get_subtile_priority(get_current_tile(), E->key());
- if (E->key() != edited_shape_coord) {
- queue_others.push_back(E->key());
- }
- }
- }
- spin_priority->set_suffix(" / " + String::num(total, 0));
- draw_highlight_subtile(edited_shape_coord, queue_others);
- } break;
- case EDITMODE_Z_INDEX: {
- spin_z_index->set_value(tileset->autotile_get_z_index(get_current_tile(), edited_shape_coord));
- draw_highlight_subtile(edited_shape_coord);
- } break;
- default: {
- }
- }
- }
-
- RID current_texture_rid = get_current_texture()->get_rid();
- List<int> *tiles = new List<int>();
- tileset->get_tile_list(tiles);
- for (List<int>::Element *E = tiles->front(); E; E = E->next()) {
- int t_id = E->get();
- if (tileset->tile_get_texture(t_id)->get_rid() == current_texture_rid && (t_id != get_current_tile() || edit_mode != EDITMODE_REGION || workspace_mode != WORKSPACE_EDIT)) {
- Rect2i region = tileset->tile_get_region(t_id);
- region.position += WORKSPACE_MARGIN;
- Color c;
- if (tileset->tile_get_tile_mode(t_id) == TileSet::SINGLE_TILE) {
- c = COLOR_SINGLE;
- } else if (tileset->tile_get_tile_mode(t_id) == TileSet::AUTO_TILE) {
- c = COLOR_AUTOTILE;
- } else if (tileset->tile_get_tile_mode(t_id) == TileSet::ATLAS_TILE) {
- c = COLOR_ATLAS;
- }
- draw_tile_subdivision(t_id, COLOR_SUBDIVISION);
- workspace->draw_rect(region, c, false);
- }
- }
- delete tiles;
-
- if (edit_mode == EDITMODE_REGION) {
- if (workspace_mode != WORKSPACE_EDIT) {
- Rect2i region = edited_region;
- Color c;
- if (workspace_mode == WORKSPACE_CREATE_SINGLE) {
- c = COLOR_SINGLE;
- } else if (workspace_mode == WORKSPACE_CREATE_AUTOTILE) {
- c = COLOR_AUTOTILE;
- } else if (workspace_mode == WORKSPACE_CREATE_ATLAS) {
- c = COLOR_ATLAS;
- }
- workspace->draw_rect(region, c, false);
- draw_edited_region_subdivision();
- } else {
- int t_id = get_current_tile();
- if (t_id < 0) {
- return;
- }
-
- Rect2i region;
- if (draw_edited_region) {
- region = edited_region;
- } else {
- region = tileset->tile_get_region(t_id);
- region.position += WORKSPACE_MARGIN;
- }
-
- if (draw_edited_region) {
- draw_edited_region_subdivision();
- } else {
- draw_tile_subdivision(t_id, COLOR_SUBDIVISION);
- }
-
- Color c;
- if (tileset->tile_get_tile_mode(t_id) == TileSet::SINGLE_TILE) {
- c = COLOR_SINGLE;
- } else if (tileset->tile_get_tile_mode(t_id) == TileSet::AUTO_TILE) {
- c = COLOR_AUTOTILE;
- } else if (tileset->tile_get_tile_mode(t_id) == TileSet::ATLAS_TILE) {
- c = COLOR_ATLAS;
- }
- workspace->draw_rect(region, c, false);
- }
- }
-
- workspace_overlay->update();
-}
-
-void TileSetEditor::_on_workspace_process() {
- if (Input::get_singleton()->is_key_pressed(KEY_ALT) || tools[VISIBLE_INFO]->is_pressed()) {
- if (!tile_names_visible) {
- tile_names_visible = true;
- workspace_overlay->update();
- }
- } else if (tile_names_visible) {
- tile_names_visible = false;
- workspace_overlay->update();
- }
-}
-
-void TileSetEditor::_on_workspace_overlay_draw() {
- if (!tileset.is_valid() || !get_current_texture().is_valid()) {
- return;
- }
-
- const Color COLOR_AUTOTILE = Color(0.266373, 0.565288, 0.988281);
- const Color COLOR_SINGLE = Color(0.988281, 0.909323, 0.266373);
- const Color COLOR_ATLAS = Color(0.78653, 0.812835, 0.832031);
-
- if (tile_names_visible) {
- RID current_texture_rid = get_current_texture()->get_rid();
- List<int> *tiles = new List<int>();
- tileset->get_tile_list(tiles);
- for (List<int>::Element *E = tiles->front(); E; E = E->next()) {
- int t_id = E->get();
- if (tileset->tile_get_texture(t_id)->get_rid() != current_texture_rid) {
- continue;
- }
-
- Rect2 region = tileset->tile_get_region(t_id);
- region.position += WORKSPACE_MARGIN;
- region.position *= workspace->get_scale().x;
- Color c;
- if (tileset->tile_get_tile_mode(t_id) == TileSet::SINGLE_TILE) {
- c = COLOR_SINGLE;
- } else if (tileset->tile_get_tile_mode(t_id) == TileSet::AUTO_TILE) {
- c = COLOR_AUTOTILE;
- } else if (tileset->tile_get_tile_mode(t_id) == TileSet::ATLAS_TILE) {
- c = COLOR_ATLAS;
- }
- String tile_id_name = String::num(t_id, 0) + ": " + tileset->tile_get_name(t_id);
- Ref<Font> font = get_theme_font("font", "Label");
- int font_size = get_theme_font_size("font_size", "Label");
- region.set_size(font->get_string_size(tile_id_name, font_size));
- workspace_overlay->draw_rect(region, c);
- region.position.y += region.size.y - 2;
- c = Color(0.1, 0.1, 0.1);
- workspace_overlay->draw_string(font, region.position, tile_id_name, HALIGN_LEFT, -1, font_size, c);
- }
- delete tiles;
- }
-
- int t_id = get_current_tile();
- if (t_id < 0) {
- return;
- }
-
- Ref<Texture2D> handle = get_theme_icon("EditorHandle", "EditorIcons");
- if (draw_handles) {
- for (int i = 0; i < current_shape.size(); i++) {
- workspace_overlay->draw_texture(handle, current_shape[i] * workspace->get_scale().x - handle->get_size() * 0.5);
- }
- }
-}
-
-int TileSetEditor::get_grabbed_point(const Vector2 &p_mouse_pos, real_t p_grab_threshold) {
- Transform2D xform = workspace->get_transform();
-
- int grabbed_point = -1;
- real_t min_distance = 1e10;
-
- for (int i = 0; i < current_shape.size(); i++) {
- const real_t distance = xform.xform(current_shape[i]).distance_to(xform.xform(p_mouse_pos));
- if (distance < p_grab_threshold && distance < min_distance) {
- min_distance = distance;
- grabbed_point = i;
- }
- }
-
- return grabbed_point;
-}
-
-bool TileSetEditor::is_within_grabbing_distance_of_first_point(const Vector2 &p_pos, real_t p_grab_threshold) {
- Transform2D xform = workspace->get_transform();
-
- const real_t distance = xform.xform(current_shape[0]).distance_to(xform.xform(p_pos));
-
- return distance < p_grab_threshold;
-}
-
-void TileSetEditor::_on_scroll_container_input(const Ref<InputEvent> &p_event) {
- const Ref<InputEventMouseButton> mb = p_event;
-
- if (mb.is_valid()) {
- // Zoom in/out using Ctrl + mouse wheel. This is done on the ScrollContainer
- // to allow performing this action anywhere, even if the cursor isn't
- // hovering the texture in the workspace.
- if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed() && mb->get_control()) {
- print_line("zooming in");
- _zoom_in();
- // Don't scroll up after zooming in.
- accept_event();
- } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->get_control()) {
- print_line("zooming out");
- _zoom_out();
- // Don't scroll down after zooming out.
- accept_event();
- }
- }
-}
-
-void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) {
- if (tileset.is_null() || !get_current_texture().is_valid()) {
- return;
- }
-
- static bool dragging;
- static bool erasing;
- static bool alternative;
- draw_edited_region = false;
-
- Rect2 current_tile_region = Rect2();
- if (get_current_tile() >= 0) {
- current_tile_region = tileset->tile_get_region(get_current_tile());
- }
- current_tile_region.position += WORKSPACE_MARGIN;
-
- const Ref<InputEventMouseButton> mb = p_ie;
- const Ref<InputEventMouseMotion> mm = p_ie;
-
- if (mb.is_valid()) {
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && !creating_shape) {
- if (!current_tile_region.has_point(mb->get_position())) {
- List<int> *tiles = new List<int>();
- tileset->get_tile_list(tiles);
- for (List<int>::Element *E = tiles->front(); E; E = E->next()) {
- int t_id = E->get();
- if (get_current_texture()->get_rid() == tileset->tile_get_texture(t_id)->get_rid()) {
- Rect2 r = tileset->tile_get_region(t_id);
- r.position += WORKSPACE_MARGIN;
- if (r.has_point(mb->get_position())) {
- set_current_tile(t_id);
- workspace->update();
- workspace_overlay->update();
- delete tiles;
- return;
- }
- }
- }
- delete tiles;
- }
- }
- }
- // Drag Middle Mouse
- if (mm.is_valid()) {
- if (mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE) {
- Vector2 dragged(mm->get_relative().x, mm->get_relative().y);
- scroll->set_h_scroll(scroll->get_h_scroll() - dragged.x * workspace->get_scale().x);
- scroll->set_v_scroll(scroll->get_v_scroll() - dragged.y * workspace->get_scale().x);
- }
- }
-
- if (edit_mode == EDITMODE_REGION) {
- if (mb.is_valid()) {
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- if (get_current_tile() >= 0 || workspace_mode != WORKSPACE_EDIT) {
- dragging = true;
- region_from = mb->get_position();
- edited_region = Rect2(region_from, Size2());
- workspace->update();
- workspace_overlay->update();
- return;
- }
- } else if (dragging && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
- dragging = false;
- edited_region = Rect2();
- workspace->update();
- workspace_overlay->update();
- return;
- } else if (dragging && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- dragging = false;
- update_edited_region(mb->get_position());
- edited_region.position -= WORKSPACE_MARGIN;
- if (!edited_region.has_no_area()) {
- if (get_current_tile() >= 0 && workspace_mode == WORKSPACE_EDIT) {
- undo_redo->create_action(TTR("Set Tile Region"));
- undo_redo->add_do_method(tileset.ptr(), "tile_set_region", get_current_tile(), edited_region);
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_region", get_current_tile(), tileset->tile_get_region(get_current_tile()));
-
- Size2 tile_workspace_size = edited_region.position + edited_region.size + WORKSPACE_MARGIN * 2;
- Size2 workspace_minsize = workspace->get_custom_minimum_size();
- // If the new region is bigger, just directly change the workspace size to avoid checking all other tiles.
- if (tile_workspace_size.x > workspace_minsize.x || tile_workspace_size.y > workspace_minsize.y) {
- Size2 max_workspace_size = Size2(MAX(tile_workspace_size.x, workspace_minsize.x), MAX(tile_workspace_size.y, workspace_minsize.y));
- undo_redo->add_do_method(workspace, "set_custom_minimum_size", max_workspace_size);
- undo_redo->add_undo_method(workspace, "set_custom_minimum_size", workspace_minsize);
- undo_redo->add_do_method(workspace_container, "set_custom_minimum_size", max_workspace_size);
- undo_redo->add_undo_method(workspace_container, "set_custom_minimum_size", workspace_minsize);
- undo_redo->add_do_method(workspace_overlay, "set_custom_minimum_size", max_workspace_size);
- undo_redo->add_undo_method(workspace_overlay, "set_custom_minimum_size", workspace_minsize);
- } else if (workspace_minsize.x > get_current_texture()->get_size().x + WORKSPACE_MARGIN.x * 2 || workspace_minsize.y > get_current_texture()->get_size().y + WORKSPACE_MARGIN.y * 2) {
- undo_redo->add_do_method(this, "update_workspace_minsize");
- undo_redo->add_undo_method(this, "update_workspace_minsize");
- }
-
- edited_region = Rect2();
-
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->add_do_method(workspace_overlay, "update");
- undo_redo->add_undo_method(workspace_overlay, "update");
- undo_redo->commit_action();
- } else {
- int t_id = tileset->get_last_unused_tile_id();
- undo_redo->create_action(TTR("Create Tile"));
- undo_redo->add_do_method(tileset.ptr(), "create_tile", t_id);
- undo_redo->add_undo_method(tileset.ptr(), "remove_tile", t_id);
- undo_redo->add_undo_method(this, "_validate_current_tile_id");
- undo_redo->add_do_method(tileset.ptr(), "tile_set_texture", t_id, get_current_texture());
- undo_redo->add_do_method(tileset.ptr(), "tile_set_region", t_id, edited_region);
- undo_redo->add_do_method(tileset.ptr(), "tile_set_name", t_id, get_current_texture()->get_path().get_file() + " " + String::num(t_id, 0));
- if (workspace_mode != WORKSPACE_CREATE_SINGLE) {
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_size", t_id, snap_step);
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_spacing", t_id, snap_separation.x);
- undo_redo->add_do_method(tileset.ptr(), "tile_set_tile_mode", t_id, workspace_mode == WORKSPACE_CREATE_AUTOTILE ? TileSet::AUTO_TILE : TileSet::ATLAS_TILE);
- }
-
- tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true);
- tool_editmode[EDITMODE_COLLISION]->set_pressed(true);
- edit_mode = EDITMODE_COLLISION;
-
- Size2 tile_workspace_size = edited_region.position + edited_region.size + WORKSPACE_MARGIN * 2;
- Size2 workspace_minsize = workspace->get_custom_minimum_size();
- if (tile_workspace_size.x > workspace_minsize.x || tile_workspace_size.y > workspace_minsize.y) {
- Size2 new_workspace_minsize = Size2(MAX(tile_workspace_size.x, workspace_minsize.x), MAX(tile_workspace_size.y, workspace_minsize.y));
- undo_redo->add_do_method(workspace, "set_custom_minimum_size", new_workspace_minsize);
- undo_redo->add_undo_method(workspace, "set_custom_minimum_size", workspace_minsize);
- undo_redo->add_do_method(workspace_container, "set_custom_minimum_size", new_workspace_minsize);
- undo_redo->add_undo_method(workspace_container, "set_custom_minimum_size", workspace_minsize);
- undo_redo->add_do_method(workspace_overlay, "set_custom_minimum_size", new_workspace_minsize);
- undo_redo->add_undo_method(workspace_overlay, "set_custom_minimum_size", workspace_minsize);
- }
-
- edited_region = Rect2();
-
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->add_do_method(workspace_overlay, "update");
- undo_redo->add_undo_method(workspace_overlay, "update");
- undo_redo->commit_action();
-
- set_current_tile(t_id);
- _on_workspace_mode_changed(WORKSPACE_EDIT);
- }
- } else {
- edited_region = Rect2();
- workspace->update();
- workspace_overlay->update();
- }
- return;
- }
- } else if (mm.is_valid()) {
- if (dragging) {
- update_edited_region(mm->get_position());
- draw_edited_region = true;
- workspace->update();
- workspace_overlay->update();
- return;
- }
- }
- }
-
- if (workspace_mode == WORKSPACE_EDIT) {
- if (get_current_tile() >= 0) {
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->autotile_get_size(get_current_tile());
- switch (edit_mode) {
- case EDITMODE_ICON: {
- if (mb.is_valid()) {
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && current_tile_region.has_point(mb->get_position())) {
- Vector2 coord((int)((mb->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mb->get_position().y - current_tile_region.position.y) / (spacing + size.y)));
- undo_redo->create_action(TTR("Set Tile Icon"));
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_icon_coordinate", get_current_tile(), coord);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_icon_coordinate", get_current_tile(), tileset->autotile_get_icon_coordinate(get_current_tile()));
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->commit_action();
- }
- }
- } break;
- case EDITMODE_BITMASK: {
- if (mb.is_valid()) {
- if (mb->is_pressed()) {
- if (dragging) {
- return;
- }
- if ((mb->get_button_index() == MOUSE_BUTTON_RIGHT || mb->get_button_index() == MOUSE_BUTTON_LEFT) && current_tile_region.has_point(mb->get_position())) {
- dragging = true;
- erasing = (mb->get_button_index() == MOUSE_BUTTON_RIGHT);
- alternative = Input::get_singleton()->is_key_pressed(KEY_SHIFT);
- Vector2 coord((int)((mb->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mb->get_position().y - current_tile_region.position.y) / (spacing + size.y)));
- Vector2 pos(coord.x * (spacing + size.x), coord.y * (spacing + size.y));
- pos = mb->get_position() - (pos + current_tile_region.position);
- uint32_t bit = 0;
- if (tileset->autotile_get_bitmask_mode(get_current_tile()) == TileSet::BITMASK_2X2) {
- if (pos.x < size.x / 2) {
- if (pos.y < size.y / 2) {
- bit = TileSet::BIND_TOPLEFT;
- } else {
- bit = TileSet::BIND_BOTTOMLEFT;
- }
- } else {
- if (pos.y < size.y / 2) {
- bit = TileSet::BIND_TOPRIGHT;
- } else {
- bit = TileSet::BIND_BOTTOMRIGHT;
- }
- }
- } else {
- if (pos.x < size.x / 3) {
- if (pos.y < size.y / 3) {
- bit = TileSet::BIND_TOPLEFT;
- } else if (pos.y > (size.y / 3) * 2) {
- bit = TileSet::BIND_BOTTOMLEFT;
- } else {
- bit = TileSet::BIND_LEFT;
- }
- } else if (pos.x > (size.x / 3) * 2) {
- if (pos.y < size.y / 3) {
- bit = TileSet::BIND_TOPRIGHT;
- } else if (pos.y > (size.y / 3) * 2) {
- bit = TileSet::BIND_BOTTOMRIGHT;
- } else {
- bit = TileSet::BIND_RIGHT;
- }
- } else {
- if (pos.y < size.y / 3) {
- bit = TileSet::BIND_TOP;
- } else if (pos.y > (size.y / 3) * 2) {
- bit = TileSet::BIND_BOTTOM;
- } else {
- bit = TileSet::BIND_CENTER;
- }
- }
- }
-
- uint32_t old_mask = tileset->autotile_get_bitmask(get_current_tile(), coord);
- uint32_t new_mask = old_mask;
- if (alternative) {
- new_mask &= ~bit;
- new_mask |= (bit << 16);
- } else if (erasing) {
- new_mask &= ~bit;
- new_mask &= ~(bit << 16);
- } else {
- new_mask |= bit;
- new_mask &= ~(bit << 16);
- }
-
- if (old_mask != new_mask) {
- undo_redo->create_action(TTR("Edit Tile Bitmask"));
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, new_mask);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, old_mask);
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->commit_action();
- }
- }
- } else {
- if ((erasing && mb->get_button_index() == MOUSE_BUTTON_RIGHT) || (!erasing && mb->get_button_index() == MOUSE_BUTTON_LEFT)) {
- dragging = false;
- erasing = false;
- alternative = false;
- }
- }
- }
- if (mm.is_valid()) {
- if (dragging && current_tile_region.has_point(mm->get_position())) {
- Vector2 coord((int)((mm->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mm->get_position().y - current_tile_region.position.y) / (spacing + size.y)));
- Vector2 pos(coord.x * (spacing + size.x), coord.y * (spacing + size.y));
- pos = mm->get_position() - (pos + current_tile_region.position);
- uint32_t bit = 0;
- if (tileset->autotile_get_bitmask_mode(get_current_tile()) == TileSet::BITMASK_2X2) {
- if (pos.x < size.x / 2) {
- if (pos.y < size.y / 2) {
- bit = TileSet::BIND_TOPLEFT;
- } else {
- bit = TileSet::BIND_BOTTOMLEFT;
- }
- } else {
- if (pos.y < size.y / 2) {
- bit = TileSet::BIND_TOPRIGHT;
- } else {
- bit = TileSet::BIND_BOTTOMRIGHT;
- }
- }
- } else {
- if (pos.x < size.x / 3) {
- if (pos.y < size.y / 3) {
- bit = TileSet::BIND_TOPLEFT;
- } else if (pos.y > (size.y / 3) * 2) {
- bit = TileSet::BIND_BOTTOMLEFT;
- } else {
- bit = TileSet::BIND_LEFT;
- }
- } else if (pos.x > (size.x / 3) * 2) {
- if (pos.y < size.y / 3) {
- bit = TileSet::BIND_TOPRIGHT;
- } else if (pos.y > (size.y / 3) * 2) {
- bit = TileSet::BIND_BOTTOMRIGHT;
- } else {
- bit = TileSet::BIND_RIGHT;
- }
- } else {
- if (pos.y < size.y / 3) {
- bit = TileSet::BIND_TOP;
- } else if (pos.y > (size.y / 3) * 2) {
- bit = TileSet::BIND_BOTTOM;
- } else {
- bit = TileSet::BIND_CENTER;
- }
- }
- }
-
- uint32_t old_mask = tileset->autotile_get_bitmask(get_current_tile(), coord);
- uint32_t new_mask = old_mask;
- if (alternative) {
- new_mask &= ~bit;
- new_mask |= (bit << 16);
- } else if (erasing) {
- new_mask &= ~bit;
- new_mask &= ~(bit << 16);
- } else {
- new_mask |= bit;
- new_mask &= ~(bit << 16);
- }
- if (old_mask != new_mask) {
- undo_redo->create_action(TTR("Edit Tile Bitmask"));
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, new_mask);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, old_mask);
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->commit_action();
- }
- }
- }
- } break;
- case EDITMODE_COLLISION:
- case EDITMODE_OCCLUSION:
- case EDITMODE_NAVIGATION:
- case EDITMODE_PRIORITY:
- case EDITMODE_Z_INDEX: {
- Vector2 shape_anchor = Vector2(0, 0);
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
- shape_anchor = edited_shape_coord;
- shape_anchor.x *= (size.x + spacing);
- shape_anchor.y *= (size.y + spacing);
- }
-
- const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius");
- shape_anchor += current_tile_region.position;
- if (tools[TOOL_SELECT]->is_pressed()) {
- if (mb.is_valid()) {
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- if (edit_mode != EDITMODE_PRIORITY && current_shape.size() > 0) {
- int grabbed_point = get_grabbed_point(mb->get_position(), grab_threshold);
-
- if (grabbed_point >= 0) {
- dragging_point = grabbed_point;
- workspace->update();
- return;
- }
- }
- if ((tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) && current_tile_region.has_point(mb->get_position())) {
- Vector2 coord((int)((mb->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mb->get_position().y - current_tile_region.position.y) / (spacing + size.y)));
- if (edited_shape_coord != coord) {
- edited_shape_coord = coord;
- _select_edited_shape_coord();
- }
- }
- workspace->update();
- } else if (!mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- if (edit_mode == EDITMODE_COLLISION) {
- if (dragging_point >= 0) {
- dragging_point = -1;
-
- Vector<Vector2> points;
-
- for (int i = 0; i < current_shape.size(); i++) {
- Vector2 p = current_shape[i];
- if (tools[TOOL_GRID_SNAP]->is_pressed() || tools[SHAPE_KEEP_INSIDE_TILE]->is_pressed()) {
- p = snap_point(p);
- }
- points.push_back(p - shape_anchor);
- }
-
- undo_redo->create_action(TTR("Edit Collision Polygon"));
- _set_edited_shape_points(points);
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- }
- } else if (edit_mode == EDITMODE_OCCLUSION) {
- if (dragging_point >= 0) {
- dragging_point = -1;
-
- Vector<Vector2> polygon;
- polygon.resize(current_shape.size());
- Vector2 *w = polygon.ptrw();
-
- for (int i = 0; i < current_shape.size(); i++) {
- w[i] = current_shape[i] - shape_anchor;
- }
-
- undo_redo->create_action(TTR("Edit Occlusion Polygon"));
- undo_redo->add_do_method(edited_occlusion_shape.ptr(), "set_polygon", polygon);
- undo_redo->add_undo_method(edited_occlusion_shape.ptr(), "set_polygon", edited_occlusion_shape->get_polygon());
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- }
- } else if (edit_mode == EDITMODE_NAVIGATION) {
- if (dragging_point >= 0) {
- dragging_point = -1;
-
- Vector<Vector2> polygon;
- Vector<int> indices;
- polygon.resize(current_shape.size());
- Vector2 *w = polygon.ptrw();
-
- for (int i = 0; i < current_shape.size(); i++) {
- w[i] = current_shape[i] - shape_anchor;
- indices.push_back(i);
- }
-
- undo_redo->create_action(TTR("Edit Navigation Polygon"));
- undo_redo->add_do_method(edited_navigation_shape.ptr(), "set_vertices", polygon);
- undo_redo->add_undo_method(edited_navigation_shape.ptr(), "set_vertices", edited_navigation_shape->get_vertices());
- undo_redo->add_do_method(edited_navigation_shape.ptr(), "clear_polygons");
- undo_redo->add_undo_method(edited_navigation_shape.ptr(), "clear_polygons");
- undo_redo->add_do_method(edited_navigation_shape.ptr(), "add_polygon", indices);
- undo_redo->add_undo_method(edited_navigation_shape.ptr(), "add_polygon", edited_navigation_shape->get_polygon(0));
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- }
- }
- }
- } else if (mm.is_valid()) {
- if (dragging_point >= 0) {
- current_shape.set(dragging_point, snap_point(mm->get_position()));
- workspace->update();
- }
- }
- } else if (tools[SHAPE_NEW_POLYGON]->is_pressed()) {
- if (mb.is_valid()) {
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- Vector2 pos = mb->get_position();
- pos = snap_point(pos);
- if (creating_shape) {
- if (current_shape.size() > 2) {
- if (is_within_grabbing_distance_of_first_point(mb->get_position(), grab_threshold)) {
- close_shape(shape_anchor);
- workspace->update();
- return;
- }
- }
- current_shape.push_back(pos);
- workspace->update();
- } else {
- creating_shape = true;
- _set_edited_collision_shape(Ref<ConvexPolygonShape2D>());
- current_shape.resize(0);
- current_shape.push_back(snap_point(pos));
- workspace->update();
- }
- } else if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
- if (creating_shape) {
- creating_shape = false;
- _select_edited_shape_coord();
- workspace->update();
- }
- }
- } else if (mm.is_valid()) {
- if (creating_shape) {
- workspace->update();
- }
- }
- } else if (tools[SHAPE_NEW_RECTANGLE]->is_pressed()) {
- if (mb.is_valid()) {
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- _set_edited_collision_shape(Ref<ConvexPolygonShape2D>());
- current_shape.resize(0);
- Vector2 pos = mb->get_position();
- pos = snap_point(pos);
- current_shape.push_back(pos);
- current_shape.push_back(pos);
- current_shape.push_back(pos);
- current_shape.push_back(pos);
- creating_shape = true;
- workspace->update();
- return;
- } else if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
- if (creating_shape) {
- creating_shape = false;
- _select_edited_shape_coord();
- workspace->update();
- }
- } else if (!mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- if (creating_shape) {
- // if the first two corners are within grabbing distance of one another, expand the rect to fill the tile
- if (is_within_grabbing_distance_of_first_point(current_shape[1], grab_threshold)) {
- current_shape.set(0, snap_point(shape_anchor));
- current_shape.set(1, snap_point(shape_anchor + Vector2(current_tile_region.size.x, 0)));
- current_shape.set(2, snap_point(shape_anchor + current_tile_region.size));
- current_shape.set(3, snap_point(shape_anchor + Vector2(0, current_tile_region.size.y)));
- }
-
- close_shape(shape_anchor);
- workspace->update();
- return;
- }
- }
- } else if (mm.is_valid()) {
- if (creating_shape) {
- Vector2 pos = mm->get_position();
- pos = snap_point(pos);
- Vector2 p = current_shape[2];
- current_shape.set(3, snap_point(Vector2(pos.x, p.y)));
- current_shape.set(0, snap_point(pos));
- current_shape.set(1, snap_point(Vector2(p.x, pos.y)));
- workspace->update();
- }
- }
- }
- } break;
- default: {
- }
- }
- }
- }
-}
-
-void TileSetEditor::_on_tool_clicked(int p_tool) {
- if (p_tool == BITMASK_COPY) {
- bitmask_map_copy = tileset->autotile_get_bitmask_map(get_current_tile());
- } else if (p_tool == BITMASK_PASTE) {
- undo_redo->create_action(TTR("Paste Tile Bitmask"));
- undo_redo->add_do_method(tileset.ptr(), "autotile_clear_bitmask_map", get_current_tile());
- undo_redo->add_undo_method(tileset.ptr(), "autotile_clear_bitmask_map", get_current_tile());
- for (Map<Vector2, uint32_t>::Element *E = bitmask_map_copy.front(); E; E = E->next()) {
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), E->key(), E->value());
- }
- for (Map<Vector2, uint32_t>::Element *E = tileset->autotile_get_bitmask_map(get_current_tile()).front(); E; E = E->next()) {
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), E->key(), E->value());
- }
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->commit_action();
- } else if (p_tool == BITMASK_CLEAR) {
- undo_redo->create_action(TTR("Clear Tile Bitmask"));
- undo_redo->add_do_method(tileset.ptr(), "autotile_clear_bitmask_map", get_current_tile());
- for (Map<Vector2, uint32_t>::Element *E = tileset->autotile_get_bitmask_map(get_current_tile()).front(); E; E = E->next()) {
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), E->key(), E->value());
- }
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->commit_action();
- } else if (p_tool == SHAPE_TOGGLE_TYPE) {
- if (edited_collision_shape.is_valid()) {
- Ref<ConvexPolygonShape2D> convex = edited_collision_shape;
- Ref<ConcavePolygonShape2D> concave = edited_collision_shape;
- Ref<Shape2D> previous_shape = edited_collision_shape;
- Array sd = tileset->call("tile_get_shapes", get_current_tile());
-
- if (convex.is_valid()) {
- // Make concave.
- undo_redo->create_action(TTR("Make Polygon Concave"));
- Ref<ConcavePolygonShape2D> _concave = memnew(ConcavePolygonShape2D);
- edited_collision_shape = _concave;
- _set_edited_shape_points(_get_collision_shape_points(convex));
- } else if (concave.is_valid()) {
- // Make convex.
- undo_redo->create_action(TTR("Make Polygon Convex"));
- Ref<ConvexPolygonShape2D> _convex = memnew(ConvexPolygonShape2D);
- edited_collision_shape = _convex;
- _set_edited_shape_points(_get_collision_shape_points(concave));
- }
- for (int i = 0; i < sd.size(); i++) {
- if (sd[i].get("shape") == previous_shape) {
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd.duplicate());
- sd.remove(i);
- break;
- }
- }
-
- undo_redo->add_do_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd);
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
- undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), edited_collision_shape, Transform2D(), false, edited_shape_coord);
- } else {
- undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), edited_collision_shape, Transform2D());
- }
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
-
- _update_toggle_shape_button();
- workspace->update();
- workspace_container->update();
- helper->notify_property_list_changed();
- }
- } else if (p_tool == SELECT_NEXT) {
- _select_next_shape();
- } else if (p_tool == SELECT_PREVIOUS) {
- _select_previous_shape();
- } else if (p_tool == SHAPE_DELETE) {
- if (creating_shape) {
- creating_shape = false;
- current_shape.resize(0);
- workspace->update();
- } else {
- switch (edit_mode) {
- case EDITMODE_REGION: {
- int t_id = get_current_tile();
- if (workspace_mode == WORKSPACE_EDIT && t_id >= 0) {
- undo_redo->create_action(TTR("Remove Tile"));
- undo_redo->add_do_method(tileset.ptr(), "remove_tile", t_id);
- _undo_tile_removal(t_id);
- undo_redo->add_do_method(this, "_validate_current_tile_id");
-
- Rect2 tile_region = tileset->tile_get_region(get_current_tile());
- Size2 tile_workspace_size = tile_region.position + tile_region.size;
- if (tile_workspace_size.x > get_current_texture()->get_size().x || tile_workspace_size.y > get_current_texture()->get_size().y) {
- undo_redo->add_do_method(this, "update_workspace_minsize");
- undo_redo->add_undo_method(this, "update_workspace_minsize");
- }
-
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->add_do_method(workspace_overlay, "update");
- undo_redo->add_undo_method(workspace_overlay, "update");
- undo_redo->commit_action();
- }
- tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true);
- workspace_mode = WORKSPACE_EDIT;
- update_workspace_tile_mode();
- } break;
- case EDITMODE_COLLISION: {
- if (!edited_collision_shape.is_null()) {
- // Necessary to get the version that returns a Array instead of a Vector.
- Array sd = tileset->call("tile_get_shapes", get_current_tile());
- for (int i = 0; i < sd.size(); i++) {
- if (sd[i].get("shape") == edited_collision_shape) {
- undo_redo->create_action(TTR("Remove Collision Polygon"));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd.duplicate());
- sd.remove(i);
- undo_redo->add_do_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd);
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- break;
- }
- }
- }
- } break;
- case EDITMODE_OCCLUSION: {
- if (!edited_occlusion_shape.is_null()) {
- undo_redo->create_action(TTR("Remove Occlusion Polygon"));
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- undo_redo->add_do_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), Ref<OccluderPolygon2D>());
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), tileset->tile_get_light_occluder(get_current_tile()));
- } else {
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), Ref<OccluderPolygon2D>(), edited_shape_coord);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), tileset->autotile_get_light_occluder(get_current_tile(), edited_shape_coord), edited_shape_coord);
- }
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- }
- } break;
- case EDITMODE_NAVIGATION: {
- if (!edited_navigation_shape.is_null()) {
- undo_redo->create_action(TTR("Remove Navigation Polygon"));
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- undo_redo->add_do_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), Ref<NavigationPolygon>());
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), tileset->tile_get_navigation_polygon(get_current_tile()));
- } else {
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), Ref<NavigationPolygon>(), edited_shape_coord);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), tileset->autotile_get_navigation_polygon(get_current_tile(), edited_shape_coord), edited_shape_coord);
- }
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- }
- } break;
- default: {
- }
- }
- }
- } else if (p_tool == TOOL_SELECT || p_tool == SHAPE_NEW_POLYGON || p_tool == SHAPE_NEW_RECTANGLE) {
- if (creating_shape) {
- // Cancel Creation
- creating_shape = false;
- current_shape.resize(0);
- workspace->update();
- }
- }
-}
-
-void TileSetEditor::_on_priority_changed(float val) {
- if ((int)val == tileset->autotile_get_subtile_priority(get_current_tile(), edited_shape_coord)) {
- return;
- }
-
- undo_redo->create_action(TTR("Edit Tile Priority"));
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_subtile_priority", get_current_tile(), edited_shape_coord, (int)val);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_subtile_priority", get_current_tile(), edited_shape_coord, tileset->autotile_get_subtile_priority(get_current_tile(), edited_shape_coord));
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->commit_action();
-}
-
-void TileSetEditor::_on_z_index_changed(float val) {
- if ((int)val == tileset->autotile_get_z_index(get_current_tile(), edited_shape_coord)) {
- return;
- }
-
- undo_redo->create_action(TTR("Edit Tile Z Index"));
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_z_index", get_current_tile(), edited_shape_coord, (int)val);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_z_index", get_current_tile(), edited_shape_coord, tileset->autotile_get_z_index(get_current_tile(), edited_shape_coord));
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->commit_action();
-}
-
-void TileSetEditor::_on_grid_snap_toggled(bool p_val) {
- helper->set_snap_options_visible(p_val);
- workspace->update();
-}
-
-Vector<Vector2> TileSetEditor::_get_collision_shape_points(const Ref<Shape2D> &p_shape) {
- Ref<ConvexPolygonShape2D> convex = p_shape;
- Ref<ConcavePolygonShape2D> concave = p_shape;
- if (convex.is_valid()) {
- return convex->get_points();
- } else if (concave.is_valid()) {
- Vector<Vector2> points;
- for (int i = 0; i < concave->get_segments().size(); i += 2) {
- points.push_back(concave->get_segments()[i]);
- }
- return points;
- } else {
- return Vector<Vector2>();
- }
-}
-
-Vector<Vector2> TileSetEditor::_get_edited_shape_points() {
- return _get_collision_shape_points(edited_collision_shape);
-}
-
-void TileSetEditor::_set_edited_shape_points(const Vector<Vector2> &points) {
- Ref<ConvexPolygonShape2D> convex = edited_collision_shape;
- Ref<ConcavePolygonShape2D> concave = edited_collision_shape;
- if (convex.is_valid()) {
- undo_redo->add_do_method(convex.ptr(), "set_points", points);
- undo_redo->add_undo_method(convex.ptr(), "set_points", _get_edited_shape_points());
- } else if (concave.is_valid() && points.size() > 1) {
- PackedVector2Array segments;
- for (int i = 0; i < points.size() - 1; i++) {
- segments.push_back(points[i]);
- segments.push_back(points[i + 1]);
- }
- segments.push_back(points[points.size() - 1]);
- segments.push_back(points[0]);
- undo_redo->add_do_method(concave.ptr(), "set_segments", segments);
- undo_redo->add_undo_method(concave.ptr(), "set_segments", concave->get_segments());
- }
-}
-
-void TileSetEditor::_update_tile_data() {
- current_tile_data.clear();
- if (get_current_tile() < 0) {
- return;
- }
-
- Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(get_current_tile());
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- SubtileData data;
- for (int i = 0; i < sd.size(); i++) {
- data.collisions.push_back(sd[i].shape);
- }
- data.navigation_shape = tileset->tile_get_navigation_polygon(get_current_tile());
- data.occlusion_shape = tileset->tile_get_light_occluder(get_current_tile());
- current_tile_data[Vector2i()] = data;
- } else {
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->tile_get_region(get_current_tile()).size;
- Vector2 cell_count = (size / (tileset->autotile_get_size(get_current_tile()) + Vector2(spacing, spacing))).floor();
- for (int y = 0; y < cell_count.y; y++) {
- for (int x = 0; x < cell_count.x; x++) {
- SubtileData data;
- Vector2i coord(x, y);
- for (int i = 0; i < sd.size(); i++) {
- if (sd[i].autotile_coord == coord) {
- data.collisions.push_back(sd[i].shape);
- }
- }
- data.navigation_shape = tileset->autotile_get_navigation_polygon(get_current_tile(), coord);
- data.occlusion_shape = tileset->tile_get_light_occluder(get_current_tile());
- current_tile_data[coord] = data;
- }
- }
- }
-}
-
-void TileSetEditor::_update_toggle_shape_button() {
- Ref<ConvexPolygonShape2D> convex = edited_collision_shape;
- Ref<ConcavePolygonShape2D> concave = edited_collision_shape;
- separator_shape_toggle->show();
- tools[SHAPE_TOGGLE_TYPE]->show();
- if (edit_mode != EDITMODE_COLLISION || !edited_collision_shape.is_valid()) {
- separator_shape_toggle->hide();
- tools[SHAPE_TOGGLE_TYPE]->hide();
- } else if (concave.is_valid()) {
- tools[SHAPE_TOGGLE_TYPE]->set_icon(get_theme_icon("ConvexPolygonShape2D", "EditorIcons"));
- tools[SHAPE_TOGGLE_TYPE]->set_text(TTR("Make Convex"));
- } else if (convex.is_valid()) {
- tools[SHAPE_TOGGLE_TYPE]->set_icon(get_theme_icon("ConcavePolygonShape2D", "EditorIcons"));
- tools[SHAPE_TOGGLE_TYPE]->set_text(TTR("Make Concave"));
- } else {
- // Shouldn't happen
- separator_shape_toggle->hide();
- tools[SHAPE_TOGGLE_TYPE]->hide();
- }
-}
-
-void TileSetEditor::_select_next_tile() {
- Array tiles = _get_tiles_in_current_texture(true);
- if (tiles.size() == 0) {
- set_current_tile(-1);
- } else if (get_current_tile() == -1) {
- set_current_tile(tiles[0]);
- } else {
- int index = tiles.find(get_current_tile());
- if (index < 0) {
- set_current_tile(tiles[0]);
- } else if (index == tiles.size() - 1) {
- set_current_tile(tiles[0]);
- } else {
- set_current_tile(tiles[index + 1]);
- }
- }
- if (get_current_tile() == -1) {
- return;
- } else if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- return;
- } else {
- switch (edit_mode) {
- case EDITMODE_COLLISION:
- case EDITMODE_OCCLUSION:
- case EDITMODE_NAVIGATION:
- case EDITMODE_PRIORITY:
- case EDITMODE_Z_INDEX: {
- edited_shape_coord = Vector2();
- _select_edited_shape_coord();
- } break;
- default: {
- }
- }
- }
-}
-
-void TileSetEditor::_select_previous_tile() {
- Array tiles = _get_tiles_in_current_texture(true);
- if (tiles.size() == 0) {
- set_current_tile(-1);
- } else if (get_current_tile() == -1) {
- set_current_tile(tiles[tiles.size() - 1]);
- } else {
- int index = tiles.find(get_current_tile());
- if (index <= 0) {
- set_current_tile(tiles[tiles.size() - 1]);
- } else {
- set_current_tile(tiles[index - 1]);
- }
- }
- if (get_current_tile() == -1) {
- return;
- } else if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- return;
- } else {
- switch (edit_mode) {
- case EDITMODE_COLLISION:
- case EDITMODE_OCCLUSION:
- case EDITMODE_NAVIGATION:
- case EDITMODE_PRIORITY:
- case EDITMODE_Z_INDEX: {
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->tile_get_region(get_current_tile()).size;
- Vector2 cell_count = (size / (tileset->autotile_get_size(get_current_tile()) + Vector2(spacing, spacing))).floor();
- cell_count -= Vector2(1, 1);
- edited_shape_coord = cell_count;
- _select_edited_shape_coord();
- } break;
- default: {
- }
- }
- }
-}
-
-Array TileSetEditor::_get_tiles_in_current_texture(bool sorted) {
- Array a;
- List<int> all_tiles;
- if (!get_current_texture().is_valid()) {
- return a;
- }
- tileset->get_tile_list(&all_tiles);
- for (int i = 0; i < all_tiles.size(); i++) {
- if (tileset->tile_get_texture(all_tiles[i]) == get_current_texture()) {
- a.push_back(all_tiles[i]);
- }
- }
- if (sorted) {
- a.sort_custom(callable_mp(this, &TileSetEditor::_sort_tiles));
- }
- return a;
-}
-
-bool TileSetEditor::_sort_tiles(Variant p_a, Variant p_b) {
- int a = p_a;
- int b = p_b;
-
- Vector2 pos_a = tileset->tile_get_region(a).position;
- Vector2 pos_b = tileset->tile_get_region(b).position;
- if (pos_a.y < pos_b.y) {
- return true;
-
- } else if (pos_a.y == pos_b.y) {
- return (pos_a.x < pos_b.x);
- } else {
- return false;
- }
-}
-
-void TileSetEditor::_select_next_subtile() {
- if (get_current_tile() == -1) {
- _select_next_tile();
- return;
- }
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- _select_next_tile();
- } else if (edit_mode == EDITMODE_REGION || edit_mode == EDITMODE_BITMASK || edit_mode == EDITMODE_ICON) {
- _select_next_tile();
- } else {
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->tile_get_region(get_current_tile()).size;
- Vector2 cell_count = (size / (tileset->autotile_get_size(get_current_tile()) + Vector2(spacing, spacing))).floor();
- if (edited_shape_coord.x >= cell_count.x - 1 && edited_shape_coord.y >= cell_count.y - 1) {
- _select_next_tile();
- } else {
- edited_shape_coord.x++;
- if (edited_shape_coord.x >= cell_count.x) {
- edited_shape_coord.x = 0;
- edited_shape_coord.y++;
- }
- _select_edited_shape_coord();
- }
- }
-}
-
-void TileSetEditor::_select_previous_subtile() {
- if (get_current_tile() == -1) {
- _select_previous_tile();
- return;
- }
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- _select_previous_tile();
- } else if (edit_mode == EDITMODE_REGION || edit_mode == EDITMODE_BITMASK || edit_mode == EDITMODE_ICON) {
- _select_previous_tile();
- } else {
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->tile_get_region(get_current_tile()).size;
- Vector2 cell_count = (size / (tileset->autotile_get_size(get_current_tile()) + Vector2(spacing, spacing))).floor();
- if (edited_shape_coord.x <= 0 && edited_shape_coord.y <= 0) {
- _select_previous_tile();
- } else {
- edited_shape_coord.x--;
- if (edited_shape_coord.x == -1) {
- edited_shape_coord.x = cell_count.x - 1;
- edited_shape_coord.y--;
- }
- _select_edited_shape_coord();
- }
- }
-}
-
-void TileSetEditor::_select_next_shape() {
- if (get_current_tile() == -1) {
- _select_next_subtile();
- } else if (edit_mode != EDITMODE_COLLISION) {
- _select_next_subtile();
- } else {
- Vector2i edited_coord = Vector2i();
- if (tileset->tile_get_tile_mode(get_current_tile()) != TileSet::SINGLE_TILE) {
- edited_coord = Vector2i(edited_shape_coord);
- }
- SubtileData data = current_tile_data[edited_coord];
- if (data.collisions.size() == 0) {
- _select_next_subtile();
- } else {
- int index = data.collisions.find(edited_collision_shape);
- if (index < 0) {
- _set_edited_collision_shape(data.collisions[0]);
- } else if (index == data.collisions.size() - 1) {
- _select_next_subtile();
- } else {
- _set_edited_collision_shape(data.collisions[index + 1]);
- }
- }
- current_shape.resize(0);
- Rect2 current_tile_region = tileset->tile_get_region(get_current_tile());
- current_tile_region.position += WORKSPACE_MARGIN;
-
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->autotile_get_size(get_current_tile());
- Vector2 shape_anchor = edited_shape_coord;
- shape_anchor.x *= (size.x + spacing);
- shape_anchor.y *= (size.y + spacing);
- current_tile_region.position += shape_anchor;
-
- if (edited_collision_shape.is_valid()) {
- for (int i = 0; i < _get_edited_shape_points().size(); i++) {
- current_shape.push_back(_get_edited_shape_points()[i] + current_tile_region.position);
- }
- }
- workspace->update();
- workspace_container->update();
- helper->notify_property_list_changed();
- }
-}
-
-void TileSetEditor::_select_previous_shape() {
- if (get_current_tile() == -1) {
- _select_previous_subtile();
- if (get_current_tile() != -1 && edit_mode == EDITMODE_COLLISION) {
- SubtileData data = current_tile_data[Vector2i(edited_shape_coord)];
- if (data.collisions.size() > 1) {
- _set_edited_collision_shape(data.collisions[data.collisions.size() - 1]);
- }
- } else {
- return;
- }
- } else if (edit_mode != EDITMODE_COLLISION) {
- _select_previous_subtile();
- } else {
- Vector2i edited_coord = Vector2i();
- if (tileset->tile_get_tile_mode(get_current_tile()) != TileSet::SINGLE_TILE) {
- edited_coord = Vector2i(edited_shape_coord);
- }
- SubtileData data = current_tile_data[edited_coord];
- if (data.collisions.size() == 0) {
- _select_previous_subtile();
- data = current_tile_data[Vector2i(edited_shape_coord)];
- if (data.collisions.size() > 1) {
- _set_edited_collision_shape(data.collisions[data.collisions.size() - 1]);
- }
- } else {
- int index = data.collisions.find(edited_collision_shape);
- if (index < 0) {
- _set_edited_collision_shape(data.collisions[data.collisions.size() - 1]);
- } else if (index == 0) {
- _select_previous_subtile();
- data = current_tile_data[Vector2i(edited_shape_coord)];
- if (data.collisions.size() > 1) {
- _set_edited_collision_shape(data.collisions[data.collisions.size() - 1]);
- }
- } else {
- _set_edited_collision_shape(data.collisions[index - 1]);
- }
- }
-
- current_shape.resize(0);
- Rect2 current_tile_region = tileset->tile_get_region(get_current_tile());
- current_tile_region.position += WORKSPACE_MARGIN;
-
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->autotile_get_size(get_current_tile());
- Vector2 shape_anchor = edited_shape_coord;
- shape_anchor.x *= (size.x + spacing);
- shape_anchor.y *= (size.y + spacing);
- current_tile_region.position += shape_anchor;
-
- if (edited_collision_shape.is_valid()) {
- for (int i = 0; i < _get_edited_shape_points().size(); i++) {
- current_shape.push_back(_get_edited_shape_points()[i] + current_tile_region.position);
- }
- }
- workspace->update();
- workspace_container->update();
- helper->notify_property_list_changed();
- }
-}
-
-void TileSetEditor::_set_edited_collision_shape(const Ref<Shape2D> &p_shape) {
- edited_collision_shape = p_shape;
- _update_toggle_shape_button();
-}
-
-void TileSetEditor::_set_snap_step(Vector2 p_val) {
- snap_step.x = CLAMP(p_val.x, 1, 256);
- snap_step.y = CLAMP(p_val.y, 1, 256);
- workspace->update();
-}
-
-void TileSetEditor::_set_snap_off(Vector2 p_val) {
- snap_offset.x = CLAMP(p_val.x, 0, 256 + WORKSPACE_MARGIN.x);
- snap_offset.y = CLAMP(p_val.y, 0, 256 + WORKSPACE_MARGIN.y);
- workspace->update();
-}
-
-void TileSetEditor::_set_snap_sep(Vector2 p_val) {
- snap_separation.x = CLAMP(p_val.x, 0, 256);
- snap_separation.y = CLAMP(p_val.y, 0, 256);
- workspace->update();
-}
-
-void TileSetEditor::_validate_current_tile_id() {
- if (get_current_tile() >= 0 && !tileset->has_tile(get_current_tile())) {
- set_current_tile(-1);
- }
-}
-
-void TileSetEditor::_select_edited_shape_coord() {
- select_coord(edited_shape_coord);
-}
-
-void TileSetEditor::_undo_tile_removal(int p_id) {
- undo_redo->add_undo_method(tileset.ptr(), "create_tile", p_id);
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_name", p_id, tileset->tile_get_name(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_texture_offset", p_id, tileset->tile_get_texture_offset(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_material", p_id, tileset->tile_get_material(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_modulate", p_id, tileset->tile_get_modulate(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_occluder_offset", p_id, tileset->tile_get_occluder_offset(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon_offset", p_id, tileset->tile_get_navigation_polygon_offset(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_shape_offset", p_id, 0, tileset->tile_get_shape_offset(p_id, 0));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_shape_transform", p_id, 0, tileset->tile_get_shape_transform(p_id, 0));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_z_index", p_id, tileset->tile_get_z_index(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_texture", p_id, tileset->tile_get_texture(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_region", p_id, tileset->tile_get_region(p_id));
- // Necessary to get the version that returns a Array instead of a Vector.
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", p_id, tileset->call("tile_get_shapes", p_id));
- if (tileset->tile_get_tile_mode(p_id) == TileSet::SINGLE_TILE) {
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_light_occluder", p_id, tileset->tile_get_light_occluder(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon", p_id, tileset->tile_get_navigation_polygon(p_id));
- } else {
- Map<Vector2, Ref<OccluderPolygon2D>> oclusion_map = tileset->autotile_get_light_oclusion_map(p_id);
- for (Map<Vector2, Ref<OccluderPolygon2D>>::Element *E = oclusion_map.front(); E; E = E->next()) {
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_light_occluder", p_id, E->value(), E->key());
- }
- Map<Vector2, Ref<NavigationPolygon>> navigation_map = tileset->autotile_get_navigation_map(p_id);
- for (Map<Vector2, Ref<NavigationPolygon>>::Element *E = navigation_map.front(); E; E = E->next()) {
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_navigation_polygon", p_id, E->value(), E->key());
- }
- Map<Vector2, uint32_t> bitmask_map = tileset->autotile_get_bitmask_map(p_id);
- for (Map<Vector2, uint32_t>::Element *E = bitmask_map.front(); E; E = E->next()) {
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", p_id, E->key(), E->value());
- }
- Map<Vector2, int> priority_map = tileset->autotile_get_priority_map(p_id);
- for (Map<Vector2, int>::Element *E = priority_map.front(); E; E = E->next()) {
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_subtile_priority", p_id, E->key(), E->value());
- }
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_icon_coordinate", p_id, tileset->autotile_get_icon_coordinate(p_id));
- Map<Vector2, int> z_map = tileset->autotile_get_z_index_map(p_id);
- for (Map<Vector2, int>::Element *E = z_map.front(); E; E = E->next()) {
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_z_index", p_id, E->key(), E->value());
- }
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_tile_mode", p_id, tileset->tile_get_tile_mode(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_size", p_id, tileset->autotile_get_size(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_spacing", p_id, tileset->autotile_get_spacing(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask_mode", p_id, tileset->autotile_get_bitmask_mode(p_id));
- }
-}
-
-void TileSetEditor::_zoom_in() {
- float scale = workspace->get_scale().x;
- if (scale < max_scale) {
- scale *= scale_ratio;
- workspace->set_scale(Vector2(scale, scale));
- workspace_container->set_custom_minimum_size(workspace->get_rect().size * scale);
- workspace_overlay->set_custom_minimum_size(workspace->get_rect().size * scale);
- }
-}
-
-void TileSetEditor::_zoom_out() {
- float scale = workspace->get_scale().x;
- if (scale > min_scale) {
- scale /= scale_ratio;
- workspace->set_scale(Vector2(scale, scale));
- workspace_container->set_custom_minimum_size(workspace->get_rect().size * scale);
- workspace_overlay->set_custom_minimum_size(workspace->get_rect().size * scale);
- }
-}
-
-void TileSetEditor::_zoom_reset() {
- workspace->set_scale(Vector2(1, 1));
- workspace_container->set_custom_minimum_size(workspace->get_rect().size);
- workspace_overlay->set_custom_minimum_size(workspace->get_rect().size);
-}
-
-void TileSetEditor::draw_highlight_current_tile() {
- Color shadow_color = Color(0.3, 0.3, 0.3, 0.3);
- if ((workspace_mode == WORKSPACE_EDIT && get_current_tile() >= 0) || !edited_region.has_no_area()) {
- Rect2 region;
- if (edited_region.has_no_area()) {
- region = tileset->tile_get_region(get_current_tile());
- region.position += WORKSPACE_MARGIN;
- } else {
- region = edited_region;
- }
-
- if (region.position.y >= 0) {
- workspace->draw_rect(Rect2(0, 0, workspace->get_rect().size.x, region.position.y), shadow_color);
- }
- if (region.position.x >= 0) {
- workspace->draw_rect(Rect2(0, MAX(0, region.position.y), region.position.x, MIN(workspace->get_rect().size.y - region.position.y, MIN(region.size.y, region.position.y + region.size.y))), shadow_color);
- }
- if (region.position.x + region.size.x <= workspace->get_rect().size.x) {
- workspace->draw_rect(Rect2(region.position.x + region.size.x, MAX(0, region.position.y), workspace->get_rect().size.x - region.position.x - region.size.x, MIN(workspace->get_rect().size.y - region.position.y, MIN(region.size.y, region.position.y + region.size.y))), shadow_color);
- }
- if (region.position.y + region.size.y <= workspace->get_rect().size.y) {
- workspace->draw_rect(Rect2(0, region.position.y + region.size.y, workspace->get_rect().size.x, workspace->get_rect().size.y - region.size.y - region.position.y), shadow_color);
- }
- } else {
- workspace->draw_rect(Rect2(Point2(0, 0), workspace->get_rect().size), shadow_color);
- }
-}
-
-void TileSetEditor::draw_highlight_subtile(Vector2 coord, const Vector<Vector2> &other_highlighted) {
- Color shadow_color = Color(0.3, 0.3, 0.3, 0.3);
- Vector2 size = tileset->autotile_get_size(get_current_tile());
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Rect2 region = tileset->tile_get_region(get_current_tile());
- coord.x *= (size.x + spacing);
- coord.y *= (size.y + spacing);
- coord += region.position;
- coord += WORKSPACE_MARGIN;
-
- if (coord.y >= 0) {
- workspace->draw_rect(Rect2(0, 0, workspace->get_rect().size.x, coord.y), shadow_color);
- }
- if (coord.x >= 0) {
- workspace->draw_rect(Rect2(0, MAX(0, coord.y), coord.x, MIN(workspace->get_rect().size.y - coord.y, MIN(size.y, coord.y + size.y))), shadow_color);
- }
- if (coord.x + size.x <= workspace->get_rect().size.x) {
- workspace->draw_rect(Rect2(coord.x + size.x, MAX(0, coord.y), workspace->get_rect().size.x - coord.x - size.x, MIN(workspace->get_rect().size.y - coord.y, MIN(size.y, coord.y + size.y))), shadow_color);
- }
- if (coord.y + size.y <= workspace->get_rect().size.y) {
- workspace->draw_rect(Rect2(0, coord.y + size.y, workspace->get_rect().size.x, workspace->get_rect().size.y - size.y - coord.y), shadow_color);
- }
-
- coord += Vector2(1, 1) / workspace->get_scale().x;
- workspace->draw_rect(Rect2(coord, size - Vector2(2, 2) / workspace->get_scale().x), Color(1, 0, 0), false);
- for (int i = 0; i < other_highlighted.size(); i++) {
- coord = other_highlighted[i];
- coord.x *= (size.x + spacing);
- coord.y *= (size.y + spacing);
- coord += region.position;
- coord += WORKSPACE_MARGIN;
- coord += Vector2(1, 1) / workspace->get_scale().x;
- workspace->draw_rect(Rect2(coord, size - Vector2(2, 2) / workspace->get_scale().x), Color(1, 0.5, 0.5), false);
- }
-}
-
-void TileSetEditor::draw_tile_subdivision(int p_id, Color p_color) const {
- Color c = p_color;
- if (tileset->tile_get_tile_mode(p_id) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(p_id) == TileSet::ATLAS_TILE) {
- Rect2 region = tileset->tile_get_region(p_id);
- Size2 size = tileset->autotile_get_size(p_id);
- int spacing = tileset->autotile_get_spacing(p_id);
- float j = size.x;
-
- while (j < region.size.x) {
- if (spacing <= 0) {
- workspace->draw_line(region.position + WORKSPACE_MARGIN + Point2(j, 0), region.position + WORKSPACE_MARGIN + Point2(j, region.size.y), c);
- } else {
- workspace->draw_rect(Rect2(region.position + WORKSPACE_MARGIN + Point2(j, 0), Size2(spacing, region.size.y)), c);
- }
- j += spacing + size.x;
- }
- j = size.y;
- while (j < region.size.y) {
- if (spacing <= 0) {
- workspace->draw_line(region.position + WORKSPACE_MARGIN + Point2(0, j), region.position + WORKSPACE_MARGIN + Point2(region.size.x, j), c);
- } else {
- workspace->draw_rect(Rect2(region.position + WORKSPACE_MARGIN + Point2(0, j), Size2(region.size.x, spacing)), c);
- }
- j += spacing + size.y;
- }
- }
-}
-
-void TileSetEditor::draw_edited_region_subdivision() const {
- Color c = Color(0.3, 0.7, 0.6);
- Rect2 region = edited_region;
- Size2 size;
- int spacing;
- bool draw;
-
- if (workspace_mode == WORKSPACE_EDIT) {
- int p_id = get_current_tile();
- size = tileset->autotile_get_size(p_id);
- spacing = tileset->autotile_get_spacing(p_id);
- draw = tileset->tile_get_tile_mode(p_id) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(p_id) == TileSet::ATLAS_TILE;
- } else {
- size = snap_step;
- spacing = snap_separation.x;
- draw = workspace_mode != WORKSPACE_CREATE_SINGLE;
- }
-
- if (draw) {
- float j = size.x;
- while (j < region.size.x) {
- if (spacing <= 0) {
- workspace->draw_line(region.position + Point2(j, 0), region.position + Point2(j, region.size.y), c);
- } else {
- workspace->draw_rect(Rect2(region.position + Point2(j, 0), Size2(spacing, region.size.y)), c);
- }
- j += spacing + size.x;
- }
- j = size.y;
- while (j < region.size.y) {
- if (spacing <= 0) {
- workspace->draw_line(region.position + Point2(0, j), region.position + Point2(region.size.x, j), c);
- } else {
- workspace->draw_rect(Rect2(region.position + Point2(0, j), Size2(region.size.x, spacing)), c);
- }
- j += spacing + size.y;
- }
- }
-}
-
-void TileSetEditor::draw_grid_snap() {
- if (tools[TOOL_GRID_SNAP]->is_pressed()) {
- Color grid_color = Color(0.4, 0, 1);
- Size2 s = workspace->get_size();
-
- int width_count = Math::floor((s.width - WORKSPACE_MARGIN.x) / (snap_step.x + snap_separation.x));
- int height_count = Math::floor((s.height - WORKSPACE_MARGIN.y) / (snap_step.y + snap_separation.y));
-
- int last_p = 0;
- if (snap_step.x != 0) {
- for (int i = 0; i <= width_count; i++) {
- if (i == 0 && snap_offset.x != 0) {
- last_p = snap_offset.x;
- }
- if (snap_separation.x != 0) {
- if (i != 0) {
- workspace->draw_rect(Rect2(last_p, 0, snap_separation.x, s.height), grid_color);
- last_p += snap_separation.x;
- } else {
- workspace->draw_rect(Rect2(last_p, 0, -snap_separation.x, s.height), grid_color);
- }
- } else {
- workspace->draw_line(Point2(last_p, 0), Point2(last_p, s.height), grid_color);
- }
- last_p += snap_step.x;
- }
- }
- last_p = 0;
- if (snap_step.y != 0) {
- for (int i = 0; i <= height_count; i++) {
- if (i == 0 && snap_offset.y != 0) {
- last_p = snap_offset.y;
- }
- if (snap_separation.y != 0) {
- if (i != 0) {
- workspace->draw_rect(Rect2(0, last_p, s.width, snap_separation.y), grid_color);
- last_p += snap_separation.y;
- } else {
- workspace->draw_rect(Rect2(0, last_p, s.width, -snap_separation.y), grid_color);
- }
- } else {
- workspace->draw_line(Point2(0, last_p), Point2(s.width, last_p), grid_color);
- }
- last_p += snap_step.y;
- }
- }
- }
-}
-
-void TileSetEditor::draw_polygon_shapes() {
- int t_id = get_current_tile();
- if (t_id < 0) {
- return;
- }
-
- switch (edit_mode) {
- case EDITMODE_COLLISION: {
- Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(t_id);
- for (int i = 0; i < sd.size(); i++) {
- Vector2 coord = Vector2(0, 0);
- Vector2 anchor = Vector2(0, 0);
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
- coord = sd[i].autotile_coord;
- anchor = tileset->autotile_get_size(t_id);
- anchor.x += tileset->autotile_get_spacing(t_id);
- anchor.y += tileset->autotile_get_spacing(t_id);
- anchor.x *= coord.x;
- anchor.y *= coord.y;
- }
- anchor += WORKSPACE_MARGIN;
- anchor += tileset->tile_get_region(t_id).position;
- Ref<Shape2D> shape = sd[i].shape;
- if (shape.is_valid()) {
- Color c_bg;
- Color c_border;
- Ref<ConvexPolygonShape2D> convex = shape;
- bool is_convex = convex.is_valid();
- if ((tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE || coord == edited_shape_coord) && sd[i].shape == edited_collision_shape) {
- if (is_convex) {
- c_bg = Color(0, 1, 1, 0.5);
- c_border = Color(0, 1, 1);
- } else {
- c_bg = Color(0.8, 0, 1, 0.5);
- c_border = Color(0.8, 0, 1);
- }
- } else {
- if (is_convex) {
- c_bg = Color(0.9, 0.7, 0.07, 0.5);
- c_border = Color(0.9, 0.7, 0.07, 1);
-
- } else {
- c_bg = Color(0.9, 0.45, 0.075, 0.5);
- c_border = Color(0.9, 0.45, 0.075);
- }
- }
- Vector<Vector2> polygon;
- Vector<Color> colors;
- if (!creating_shape && shape == edited_collision_shape && current_shape.size() > 2) {
- for (int j = 0; j < current_shape.size(); j++) {
- polygon.push_back(current_shape[j]);
- colors.push_back(c_bg);
- }
- } else {
- for (int j = 0; j < _get_collision_shape_points(shape).size(); j++) {
- polygon.push_back(_get_collision_shape_points(shape)[j] + anchor);
- colors.push_back(c_bg);
- }
- }
-
- if (polygon.size() < 3) {
- continue;
- }
-
- workspace->draw_polygon(polygon, colors);
-
- if (coord == edited_shape_coord || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- if (!creating_shape && polygon.size() > 1) {
- for (int j = 0; j < polygon.size() - 1; j++) {
- workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1);
- }
- workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1);
- }
- if (shape == edited_collision_shape) {
- draw_handles = true;
- }
- }
- }
- }
- } break;
- case EDITMODE_OCCLUSION: {
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- Ref<OccluderPolygon2D> shape = edited_occlusion_shape;
- if (shape.is_valid()) {
- Color c_bg = Color(0, 1, 1, 0.5);
- Color c_border = Color(0, 1, 1);
-
- Vector<Vector2> polygon;
- Vector<Color> colors;
- Vector2 anchor = WORKSPACE_MARGIN;
- anchor += tileset->tile_get_region(get_current_tile()).position;
- if (!creating_shape && shape == edited_occlusion_shape && current_shape.size() > 2) {
- for (int j = 0; j < current_shape.size(); j++) {
- polygon.push_back(current_shape[j]);
- colors.push_back(c_bg);
- }
- } else {
- for (int j = 0; j < shape->get_polygon().size(); j++) {
- polygon.push_back(shape->get_polygon()[j] + anchor);
- colors.push_back(c_bg);
- }
- }
- workspace->draw_polygon(polygon, colors);
-
- if (!creating_shape && polygon.size() > 1) {
- for (int j = 0; j < polygon.size() - 1; j++) {
- workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1);
- }
- workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1);
- }
- if (shape == edited_occlusion_shape) {
- draw_handles = true;
- }
- }
- } else {
- Map<Vector2, Ref<OccluderPolygon2D>> map = tileset->autotile_get_light_oclusion_map(t_id);
- for (Map<Vector2, Ref<OccluderPolygon2D>>::Element *E = map.front(); E; E = E->next()) {
- Vector2 coord = E->key();
- Vector2 anchor = tileset->autotile_get_size(t_id);
- anchor.x += tileset->autotile_get_spacing(t_id);
- anchor.y += tileset->autotile_get_spacing(t_id);
- anchor.x *= coord.x;
- anchor.y *= coord.y;
- anchor += WORKSPACE_MARGIN;
- anchor += tileset->tile_get_region(t_id).position;
- Ref<OccluderPolygon2D> shape = E->value();
- if (shape.is_valid()) {
- Color c_bg;
- Color c_border;
- if (coord == edited_shape_coord && shape == edited_occlusion_shape) {
- c_bg = Color(0, 1, 1, 0.5);
- c_border = Color(0, 1, 1);
- } else {
- c_bg = Color(0.9, 0.7, 0.07, 0.5);
- c_border = Color(0.9, 0.7, 0.07, 1);
- }
- Vector<Vector2> polygon;
- Vector<Color> colors;
- if (!creating_shape && shape == edited_occlusion_shape && current_shape.size() > 2) {
- for (int j = 0; j < current_shape.size(); j++) {
- polygon.push_back(current_shape[j]);
- colors.push_back(c_bg);
- }
- } else {
- for (int j = 0; j < shape->get_polygon().size(); j++) {
- polygon.push_back(shape->get_polygon()[j] + anchor);
- colors.push_back(c_bg);
- }
- }
- workspace->draw_polygon(polygon, colors);
-
- if (coord == edited_shape_coord) {
- if (!creating_shape && polygon.size() > 1) {
- for (int j = 0; j < polygon.size() - 1; j++) {
- workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1);
- }
- workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1);
- }
- if (shape == edited_occlusion_shape) {
- draw_handles = true;
- }
- }
- }
- }
- }
- } break;
- case EDITMODE_NAVIGATION: {
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- Ref<NavigationPolygon> shape = edited_navigation_shape;
-
- if (shape.is_valid()) {
- Color c_bg = Color(0, 1, 1, 0.5);
- Color c_border = Color(0, 1, 1);
-
- Vector<Vector2> polygon;
- Vector<Color> colors;
- Vector2 anchor = WORKSPACE_MARGIN;
- anchor += tileset->tile_get_region(get_current_tile()).position;
- if (!creating_shape && shape == edited_navigation_shape && current_shape.size() > 2) {
- for (int j = 0; j < current_shape.size(); j++) {
- polygon.push_back(current_shape[j]);
- colors.push_back(c_bg);
- }
- } else {
- Vector<Vector2> vertices = shape->get_vertices();
- for (int j = 0; j < shape->get_polygon(0).size(); j++) {
- polygon.push_back(vertices[shape->get_polygon(0)[j]] + anchor);
- colors.push_back(c_bg);
- }
- }
- workspace->draw_polygon(polygon, colors);
-
- if (!creating_shape && polygon.size() > 1) {
- for (int j = 0; j < polygon.size() - 1; j++) {
- workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1);
- }
- workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1);
- }
- if (shape == edited_navigation_shape) {
- draw_handles = true;
- }
- }
- } else {
- Map<Vector2, Ref<NavigationPolygon>> map = tileset->autotile_get_navigation_map(t_id);
- for (Map<Vector2, Ref<NavigationPolygon>>::Element *E = map.front(); E; E = E->next()) {
- Vector2 coord = E->key();
- Vector2 anchor = tileset->autotile_get_size(t_id);
- anchor.x += tileset->autotile_get_spacing(t_id);
- anchor.y += tileset->autotile_get_spacing(t_id);
- anchor.x *= coord.x;
- anchor.y *= coord.y;
- anchor += WORKSPACE_MARGIN;
- anchor += tileset->tile_get_region(t_id).position;
- Ref<NavigationPolygon> shape = E->value();
- if (shape.is_valid()) {
- Color c_bg;
- Color c_border;
- if (coord == edited_shape_coord && shape == edited_navigation_shape) {
- c_bg = Color(0, 1, 1, 0.5);
- c_border = Color(0, 1, 1);
- } else {
- c_bg = Color(0.9, 0.7, 0.07, 0.5);
- c_border = Color(0.9, 0.7, 0.07, 1);
- }
- Vector<Vector2> polygon;
- Vector<Color> colors;
- if (!creating_shape && shape == edited_navigation_shape && current_shape.size() > 2) {
- for (int j = 0; j < current_shape.size(); j++) {
- polygon.push_back(current_shape[j]);
- colors.push_back(c_bg);
- }
- } else {
- Vector<Vector2> vertices = shape->get_vertices();
- for (int j = 0; j < shape->get_polygon(0).size(); j++) {
- polygon.push_back(vertices[shape->get_polygon(0)[j]] + anchor);
- colors.push_back(c_bg);
- }
- }
- workspace->draw_polygon(polygon, colors);
-
- if (coord == edited_shape_coord) {
- if (!creating_shape && polygon.size() > 1) {
- for (int j = 0; j < polygon.size() - 1; j++) {
- workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1);
- }
- workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1);
- }
- if (shape == edited_navigation_shape) {
- draw_handles = true;
- }
- }
- }
- }
- }
- } break;
- default: {
- }
- }
-
- if (creating_shape && current_shape.size() > 1) {
- for (int j = 0; j < current_shape.size() - 1; j++) {
- workspace->draw_line(current_shape[j], current_shape[j + 1], Color(0, 1, 1), 1);
- }
- workspace->draw_line(current_shape[current_shape.size() - 1], snap_point(workspace->get_local_mouse_position()), Color(0, 1, 1), 1);
- draw_handles = true;
- }
-}
-
-void TileSetEditor::close_shape(const Vector2 &shape_anchor) {
- creating_shape = false;
-
- if (edit_mode == EDITMODE_COLLISION) {
- if (current_shape.size() >= 3) {
- Ref<ConvexPolygonShape2D> shape = memnew(ConvexPolygonShape2D);
-
- Vector<Vector2> points;
- float p_total = 0;
-
- for (int i = 0; i < current_shape.size(); i++) {
- points.push_back(current_shape[i] - shape_anchor);
-
- if (i != current_shape.size() - 1) {
- p_total += ((current_shape[i + 1].x - current_shape[i].x) * (-current_shape[i + 1].y + (-current_shape[i].y)));
- } else {
- p_total += ((current_shape[0].x - current_shape[i].x) * (-current_shape[0].y + (-current_shape[i].y)));
- }
- }
-
- if (p_total < 0) {
- points.reverse();
- }
-
- shape->set_points(points);
-
- undo_redo->create_action(TTR("Create Collision Polygon"));
- // Necessary to get the version that returns a Array instead of a Vector.
- Array sd = tileset->call("tile_get_shapes", get_current_tile());
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd.duplicate());
- for (int i = 0; i < sd.size(); i++) {
- if (sd[i].get("shape") == edited_collision_shape) {
- sd.remove(i);
- break;
- }
- }
- undo_redo->add_do_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd);
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
- undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), shape, Transform2D(), false, edited_shape_coord);
- } else {
- undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), shape, Transform2D());
- }
- tools[TOOL_SELECT]->set_pressed(true);
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- } else {
- tools[TOOL_SELECT]->set_pressed(true);
- workspace->update();
- }
- } else if (edit_mode == EDITMODE_OCCLUSION) {
- Ref<OccluderPolygon2D> shape = memnew(OccluderPolygon2D);
-
- Vector<Vector2> polygon;
- polygon.resize(current_shape.size());
- Vector2 *w = polygon.ptrw();
-
- for (int i = 0; i < current_shape.size(); i++) {
- w[i] = current_shape[i] - shape_anchor;
- }
-
- shape->set_polygon(polygon);
-
- undo_redo->create_action(TTR("Create Occlusion Polygon"));
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), shape, edited_shape_coord);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), tileset->autotile_get_light_occluder(get_current_tile(), edited_shape_coord), edited_shape_coord);
- } else {
- undo_redo->add_do_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), shape);
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), tileset->tile_get_light_occluder(get_current_tile()));
- }
- tools[TOOL_SELECT]->set_pressed(true);
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- } else if (edit_mode == EDITMODE_NAVIGATION) {
- Ref<NavigationPolygon> shape = memnew(NavigationPolygon);
-
- Vector<Vector2> polygon;
- Vector<int> indices;
- polygon.resize(current_shape.size());
- Vector2 *w = polygon.ptrw();
-
- for (int i = 0; i < current_shape.size(); i++) {
- w[i] = current_shape[i] - shape_anchor;
- indices.push_back(i);
- }
-
- shape->set_vertices(polygon);
- shape->add_polygon(indices);
-
- undo_redo->create_action(TTR("Create Navigation Polygon"));
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), shape, edited_shape_coord);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), tileset->autotile_get_navigation_polygon(get_current_tile(), edited_shape_coord), edited_shape_coord);
- } else {
- undo_redo->add_do_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), shape);
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), tileset->tile_get_navigation_polygon(get_current_tile()));
- }
- tools[TOOL_SELECT]->set_pressed(true);
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- }
- tileset->notify_property_list_changed();
-}
-
-void TileSetEditor::select_coord(const Vector2 &coord) {
- _update_tile_data();
- current_shape = PackedVector2Array();
- if (get_current_tile() == -1) {
- return;
- }
- Rect2 current_tile_region = tileset->tile_get_region(get_current_tile());
- current_tile_region.position += WORKSPACE_MARGIN;
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- if (edited_collision_shape != tileset->tile_get_shape(get_current_tile(), 0)) {
- _set_edited_collision_shape(tileset->tile_get_shape(get_current_tile(), 0));
- }
- if (edited_occlusion_shape != tileset->tile_get_light_occluder(get_current_tile())) {
- edited_occlusion_shape = tileset->tile_get_light_occluder(get_current_tile());
- }
- if (edited_navigation_shape != tileset->tile_get_navigation_polygon(get_current_tile())) {
- edited_navigation_shape = tileset->tile_get_navigation_polygon(get_current_tile());
- }
-
- if (edit_mode == EDITMODE_COLLISION) {
- current_shape.resize(0);
- if (edited_collision_shape.is_valid()) {
- for (int i = 0; i < _get_edited_shape_points().size(); i++) {
- current_shape.push_back(_get_edited_shape_points()[i] + current_tile_region.position);
- }
- }
- } else if (edit_mode == EDITMODE_OCCLUSION) {
- current_shape.resize(0);
- if (edited_occlusion_shape.is_valid()) {
- for (int i = 0; i < edited_occlusion_shape->get_polygon().size(); i++) {
- current_shape.push_back(edited_occlusion_shape->get_polygon()[i] + current_tile_region.position);
- }
- }
- } else if (edit_mode == EDITMODE_NAVIGATION) {
- current_shape.resize(0);
- if (edited_navigation_shape.is_valid()) {
- if (edited_navigation_shape->get_polygon_count() > 0) {
- Vector<Vector2> vertices = edited_navigation_shape->get_vertices();
- for (int i = 0; i < edited_navigation_shape->get_polygon(0).size(); i++) {
- current_shape.push_back(vertices[edited_navigation_shape->get_polygon(0)[i]] + current_tile_region.position);
- }
- }
- }
- }
- } else {
- Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(get_current_tile());
- bool found_collision_shape = false;
- for (int i = 0; i < sd.size(); i++) {
- if (sd[i].autotile_coord == coord) {
- if (edited_collision_shape != sd[i].shape) {
- _set_edited_collision_shape(sd[i].shape);
- }
- found_collision_shape = true;
- break;
- }
- }
- if (!found_collision_shape) {
- _set_edited_collision_shape(Ref<ConvexPolygonShape2D>(nullptr));
- }
- if (edited_occlusion_shape != tileset->autotile_get_light_occluder(get_current_tile(), coord)) {
- edited_occlusion_shape = tileset->autotile_get_light_occluder(get_current_tile(), coord);
- }
- if (edited_navigation_shape != tileset->autotile_get_navigation_polygon(get_current_tile(), coord)) {
- edited_navigation_shape = tileset->autotile_get_navigation_polygon(get_current_tile(), coord);
- }
-
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->autotile_get_size(get_current_tile());
- Vector2 shape_anchor = coord;
- shape_anchor.x *= (size.x + spacing);
- shape_anchor.y *= (size.y + spacing);
- shape_anchor += current_tile_region.position;
- if (edit_mode == EDITMODE_COLLISION) {
- current_shape.resize(0);
- if (edited_collision_shape.is_valid()) {
- for (int j = 0; j < _get_edited_shape_points().size(); j++) {
- current_shape.push_back(_get_edited_shape_points()[j] + shape_anchor);
- }
- }
- } else if (edit_mode == EDITMODE_OCCLUSION) {
- current_shape.resize(0);
- if (edited_occlusion_shape.is_valid()) {
- for (int i = 0; i < edited_occlusion_shape->get_polygon().size(); i++) {
- current_shape.push_back(edited_occlusion_shape->get_polygon()[i] + shape_anchor);
- }
- }
- } else if (edit_mode == EDITMODE_NAVIGATION) {
- current_shape.resize(0);
- if (edited_navigation_shape.is_valid()) {
- if (edited_navigation_shape->get_polygon_count() > 0) {
- Vector<Vector2> vertices = edited_navigation_shape->get_vertices();
- for (int i = 0; i < edited_navigation_shape->get_polygon(0).size(); i++) {
- current_shape.push_back(vertices[edited_navigation_shape->get_polygon(0)[i]] + shape_anchor);
- }
- }
- }
- }
- }
- workspace->update();
- workspace_container->update();
- helper->notify_property_list_changed();
-}
-
-Vector2 TileSetEditor::snap_point(const Vector2 &point) {
- Vector2 p = point;
- Vector2 coord = edited_shape_coord;
- Vector2 tile_size = tileset->autotile_get_size(get_current_tile());
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 anchor = coord;
- anchor.x *= (tile_size.x + spacing);
- anchor.y *= (tile_size.y + spacing);
- anchor += tileset->tile_get_region(get_current_tile()).position;
- anchor += WORKSPACE_MARGIN;
- Rect2 region(anchor, tile_size);
- Rect2 tile_region(tileset->tile_get_region(get_current_tile()).position + WORKSPACE_MARGIN, tileset->tile_get_region(get_current_tile()).size);
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- region.position = tileset->tile_get_region(get_current_tile()).position + WORKSPACE_MARGIN;
- region.size = tileset->tile_get_region(get_current_tile()).size;
- }
-
- if (tools[TOOL_GRID_SNAP]->is_pressed()) {
- p.x = Math::snap_scalar_separation(snap_offset.x, snap_step.x, p.x, snap_separation.x);
- p.y = Math::snap_scalar_separation(snap_offset.y, snap_step.y, p.y, snap_separation.y);
- }
-
- if (tools[SHAPE_KEEP_INSIDE_TILE]->is_pressed()) {
- if (p.x < region.position.x) {
- p.x = region.position.x;
- }
- if (p.y < region.position.y) {
- p.y = region.position.y;
- }
- if (p.x > region.position.x + region.size.x) {
- p.x = region.position.x + region.size.x;
- }
- if (p.y > region.position.y + region.size.y) {
- p.y = region.position.y + region.size.y;
- }
- }
-
- if (p.x < tile_region.position.x) {
- p.x = tile_region.position.x;
- }
- if (p.y < tile_region.position.y) {
- p.y = tile_region.position.y;
- }
- if (p.x > (tile_region.position.x + tile_region.size.x)) {
- p.x = (tile_region.position.x + tile_region.size.x);
- }
- if (p.y > (tile_region.position.y + tile_region.size.y)) {
- p.y = (tile_region.position.y + tile_region.size.y);
- }
-
- return p;
-}
-
-void TileSetEditor::add_texture(Ref<Texture2D> p_texture) {
- texture_list->add_item(p_texture->get_path().get_file());
- texture_map.insert(p_texture->get_rid(), p_texture);
- texture_list->set_item_metadata(texture_list->get_item_count() - 1, p_texture->get_rid());
-}
-
-void TileSetEditor::remove_texture(Ref<Texture2D> p_texture) {
- texture_list->remove_item(texture_list->find_metadata(p_texture->get_rid()));
- texture_map.erase(p_texture->get_rid());
-
- _validate_current_tile_id();
-
- if (!get_current_texture().is_valid()) {
- _on_texture_list_selected(-1);
- workspace_overlay->update();
- }
-}
-
-void TileSetEditor::update_texture_list() {
- Ref<Texture2D> selected_texture = get_current_texture();
-
- helper->set_tileset(tileset);
-
- List<int> ids;
- tileset->get_tile_list(&ids);
- Vector<int> ids_to_remove;
- for (List<int>::Element *E = ids.front(); E; E = E->next()) {
- // Clear tiles referencing gone textures (user has been already given the chance to fix broken deps)
- if (!tileset->tile_get_texture(E->get()).is_valid()) {
- ids_to_remove.push_back(E->get());
- ERR_CONTINUE(!tileset->tile_get_texture(E->get()).is_valid());
- }
-
- if (!texture_map.has(tileset->tile_get_texture(E->get())->get_rid())) {
- add_texture(tileset->tile_get_texture(E->get()));
- }
- }
- for (int i = 0; i < ids_to_remove.size(); i++) {
- tileset->remove_tile(ids_to_remove[i]);
- }
-
- if (texture_list->get_item_count() > 0 && selected_texture.is_valid()) {
- texture_list->select(texture_list->find_metadata(selected_texture->get_rid()));
- if (texture_list->get_selected_items().size() > 0) {
- _on_texture_list_selected(texture_list->get_selected_items()[0]);
- }
- } else if (get_current_texture().is_valid()) {
- _on_texture_list_selected(texture_list->find_metadata(get_current_texture()->get_rid()));
- } else {
- _validate_current_tile_id();
- _on_texture_list_selected(-1);
- workspace_overlay->update();
- }
- update_texture_list_icon();
- helper->notify_property_list_changed();
-}
-
-void TileSetEditor::update_texture_list_icon() {
- for (int current_idx = 0; current_idx < texture_list->get_item_count(); current_idx++) {
- RID rid = texture_list->get_item_metadata(current_idx);
- texture_list->set_item_icon(current_idx, texture_map[rid]);
- Size2 texture_size = texture_map[rid]->get_size();
- texture_list->set_item_icon_region(current_idx, Rect2(0, 0, MIN(texture_size.x, 150), MIN(texture_size.y, 100)));
- }
- texture_list->update();
-}
-
-void TileSetEditor::update_workspace_tile_mode() {
- if (!get_current_texture().is_valid()) {
- tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true);
- workspace_mode = WORKSPACE_EDIT;
- for (int i = 1; i < WORKSPACE_MODE_MAX; i++) {
- tool_workspacemode[i]->set_disabled(true);
- }
- tools[SELECT_NEXT]->set_disabled(true);
- tools[SELECT_PREVIOUS]->set_disabled(true);
-
- tools[ZOOM_OUT]->hide();
- tools[ZOOM_1]->hide();
- tools[ZOOM_IN]->hide();
- tools[VISIBLE_INFO]->hide();
-
- scroll->hide();
- empty_message->show();
- } else {
- for (int i = 1; i < WORKSPACE_MODE_MAX; i++) {
- tool_workspacemode[i]->set_disabled(false);
- }
- tools[SELECT_NEXT]->set_disabled(false);
- tools[SELECT_PREVIOUS]->set_disabled(false);
-
- tools[ZOOM_OUT]->show();
- tools[ZOOM_1]->show();
- tools[ZOOM_IN]->show();
- tools[VISIBLE_INFO]->show();
-
- scroll->show();
- empty_message->hide();
- }
-
- if (workspace_mode != WORKSPACE_EDIT) {
- for (int i = 0; i < EDITMODE_MAX; i++) {
- tool_editmode[i]->hide();
- }
- tool_editmode[EDITMODE_REGION]->show();
- tool_editmode[EDITMODE_REGION]->set_pressed(true);
- _on_edit_mode_changed(EDITMODE_REGION);
- separator_editmode->show();
- return;
- }
-
- if (get_current_tile() < 0) {
- for (int i = 0; i < EDITMODE_MAX; i++) {
- tool_editmode[i]->hide();
- }
- for (int i = TOOL_SELECT; i < ZOOM_OUT; i++) {
- tools[i]->hide();
- }
-
- separator_editmode->hide();
- separator_bitmask->hide();
- separator_delete->hide();
- separator_grid->hide();
- return;
- }
-
- for (int i = 0; i < EDITMODE_MAX; i++) {
- tool_editmode[i]->show();
- }
- separator_editmode->show();
-
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- if (tool_editmode[EDITMODE_ICON]->is_pressed() || tool_editmode[EDITMODE_PRIORITY]->is_pressed() || tool_editmode[EDITMODE_BITMASK]->is_pressed() || tool_editmode[EDITMODE_Z_INDEX]->is_pressed()) {
- tool_editmode[EDITMODE_COLLISION]->set_pressed(true);
- edit_mode = EDITMODE_COLLISION;
- }
- select_coord(Vector2(0, 0));
-
- tool_editmode[EDITMODE_ICON]->hide();
- tool_editmode[EDITMODE_BITMASK]->hide();
- tool_editmode[EDITMODE_PRIORITY]->hide();
- tool_editmode[EDITMODE_Z_INDEX]->hide();
- } else if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE) {
- if (edit_mode == EDITMODE_ICON) {
- select_coord(tileset->autotile_get_icon_coordinate(get_current_tile()));
- } else {
- _select_edited_shape_coord();
- }
- } else if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
- if (tool_editmode[EDITMODE_PRIORITY]->is_pressed() || tool_editmode[EDITMODE_BITMASK]->is_pressed()) {
- tool_editmode[EDITMODE_COLLISION]->set_pressed(true);
- edit_mode = EDITMODE_COLLISION;
- }
- if (edit_mode == EDITMODE_ICON) {
- select_coord(tileset->autotile_get_icon_coordinate(get_current_tile()));
- } else {
- _select_edited_shape_coord();
- }
-
- tool_editmode[EDITMODE_BITMASK]->hide();
- }
- _on_edit_mode_changed(edit_mode);
-}
-
-void TileSetEditor::update_workspace_minsize() {
- Size2 workspace_min_size = get_current_texture()->get_size();
- RID current_texture_rid = get_current_texture()->get_rid();
- List<int> *tiles = new List<int>();
- tileset->get_tile_list(tiles);
- for (List<int>::Element *E = tiles->front(); E; E = E->next()) {
- if (tileset->tile_get_texture(E->get())->get_rid() != current_texture_rid) {
- continue;
- }
-
- Rect2i region = tileset->tile_get_region(E->get());
- if (region.position.x + region.size.x > workspace_min_size.x) {
- workspace_min_size.x = region.position.x + region.size.x;
- }
- if (region.position.y + region.size.y > workspace_min_size.y) {
- workspace_min_size.y = region.position.y + region.size.y;
- }
- }
- delete tiles;
-
- workspace->set_custom_minimum_size(workspace_min_size + WORKSPACE_MARGIN * 2);
- workspace_container->set_custom_minimum_size(workspace_min_size * workspace->get_scale() + WORKSPACE_MARGIN * 2);
- workspace_overlay->set_custom_minimum_size(workspace_min_size * workspace->get_scale() + WORKSPACE_MARGIN * 2);
-}
-
-void TileSetEditor::update_edited_region(const Vector2 &end_point) {
- edited_region = Rect2(region_from, Size2());
- if (tools[TOOL_GRID_SNAP]->is_pressed()) {
- Vector2 grid_coord;
- grid_coord = ((region_from - snap_offset) / (snap_step + snap_separation)).floor();
- grid_coord *= (snap_step + snap_separation);
- grid_coord += snap_offset;
- edited_region.expand_to(grid_coord);
- grid_coord += snap_step;
- edited_region.expand_to(grid_coord);
-
- grid_coord = ((end_point - snap_offset) / (snap_step + snap_separation)).floor();
- grid_coord *= (snap_step + snap_separation);
- grid_coord += snap_offset;
- edited_region.expand_to(grid_coord);
- grid_coord += snap_step;
- edited_region.expand_to(grid_coord);
- } else {
- edited_region.expand_to(end_point);
- }
-}
-
-int TileSetEditor::get_current_tile() const {
- return current_tile;
-}
-
-void TileSetEditor::set_current_tile(int p_id) {
- if (current_tile != p_id) {
- current_tile = p_id;
- helper->notify_property_list_changed();
- select_coord(Vector2(0, 0));
- update_workspace_tile_mode();
- if (p_id == -1) {
- editor->get_inspector()->edit(tileset.ptr());
- } else {
- editor->get_inspector()->edit(helper);
- }
- }
-}
-
-Ref<Texture2D> TileSetEditor::get_current_texture() {
- if (texture_list->get_selected_items().size() == 0) {
- return Ref<Texture2D>();
- } else {
- return texture_map[texture_list->get_item_metadata(texture_list->get_selected_items()[0])];
- }
-}
-
-void TilesetEditorContext::set_tileset(const Ref<TileSet> &p_tileset) {
- tileset = p_tileset;
-}
-
-void TilesetEditorContext::set_snap_options_visible(bool p_visible) {
- snap_options_visible = p_visible;
- notify_property_list_changed();
-}
-
-bool TilesetEditorContext::_set(const StringName &p_name, const Variant &p_value) {
- String name = p_name.operator String();
-
- if (name == "options_offset") {
- Vector2 snap = p_value;
- tileset_editor->_set_snap_off(snap + WORKSPACE_MARGIN);
- return true;
- } else if (name == "options_step") {
- Vector2 snap = p_value;
- tileset_editor->_set_snap_step(snap);
- return true;
- } else if (name == "options_separation") {
- Vector2 snap = p_value;
- tileset_editor->_set_snap_sep(snap);
- return true;
- } else if (p_name.operator String().left(5) == "tile_") {
- String name2 = p_name.operator String().right(5);
- bool v = false;
-
- if (tileset_editor->get_current_tile() < 0 || tileset.is_null()) {
- return false;
- }
-
- if (name2 == "autotile_bitmask_mode") {
- tileset->set(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/bitmask_mode", p_value, &v);
- } else if (name2 == "subtile_size") {
- tileset->set(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/tile_size", p_value, &v);
- } else if (name2 == "subtile_spacing") {
- tileset->set(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/spacing", p_value, &v);
- } else {
- tileset->set(String::num(tileset_editor->get_current_tile(), 0) + "/" + name2, p_value, &v);
- }
- if (v) {
- tileset->notify_property_list_changed();
- tileset_editor->workspace->update();
- tileset_editor->workspace_overlay->update();
- }
- return v;
- } else if (name == "tileset_script") {
- tileset->set_script(p_value);
- return true;
- } else if (name == "selected_collision_one_way") {
- Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(tileset_editor->get_current_tile());
- for (int index = 0; index < sd.size(); index++) {
- if (sd[index].shape == tileset_editor->edited_collision_shape) {
- tileset->tile_set_shape_one_way(tileset_editor->get_current_tile(), index, p_value);
- return true;
- }
- }
- return false;
- } else if (name == "selected_collision_one_way_margin") {
- Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(tileset_editor->get_current_tile());
- for (int index = 0; index < sd.size(); index++) {
- if (sd[index].shape == tileset_editor->edited_collision_shape) {
- tileset->tile_set_shape_one_way_margin(tileset_editor->get_current_tile(), index, p_value);
- return true;
- }
- }
- return false;
- }
-
- tileset_editor->err_dialog->set_text(TTR("This property can't be changed."));
- tileset_editor->err_dialog->popup_centered(Size2(300, 60));
- return false;
-}
-
-bool TilesetEditorContext::_get(const StringName &p_name, Variant &r_ret) const {
- String name = p_name.operator String();
- bool v = false;
-
- if (name == "options_offset") {
- r_ret = tileset_editor->snap_offset - WORKSPACE_MARGIN;
- v = true;
- } else if (name == "options_step") {
- r_ret = tileset_editor->snap_step;
- v = true;
- } else if (name == "options_separation") {
- r_ret = tileset_editor->snap_separation;
- v = true;
- } else if (name.left(5) == "tile_") {
- name = name.right(5);
-
- if (tileset_editor->get_current_tile() < 0 || tileset.is_null()) {
- return false;
- }
- if (!tileset->has_tile(tileset_editor->get_current_tile())) {
- return false;
- }
-
- if (name == "autotile_bitmask_mode") {
- r_ret = tileset->get(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/bitmask_mode", &v);
- } else if (name == "subtile_size") {
- r_ret = tileset->get(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/tile_size", &v);
- } else if (name == "subtile_spacing") {
- r_ret = tileset->get(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/spacing", &v);
- } else {
- r_ret = tileset->get(String::num(tileset_editor->get_current_tile(), 0) + "/" + name, &v);
- }
- return v;
- } else if (name == "selected_collision") {
- r_ret = tileset_editor->edited_collision_shape;
- v = true;
- } else if (name == "selected_collision_one_way") {
- Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(tileset_editor->get_current_tile());
- for (int index = 0; index < sd.size(); index++) {
- if (sd[index].shape == tileset_editor->edited_collision_shape) {
- r_ret = sd[index].one_way_collision;
- v = true;
- break;
- }
- }
- } else if (name == "selected_collision_one_way_margin") {
- Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(tileset_editor->get_current_tile());
- for (int index = 0; index < sd.size(); index++) {
- if (sd[index].shape == tileset_editor->edited_collision_shape) {
- r_ret = sd[index].one_way_collision_margin;
- v = true;
- break;
- }
- }
- } else if (name == "selected_navigation") {
- r_ret = tileset_editor->edited_navigation_shape;
- v = true;
- } else if (name == "selected_occlusion") {
- r_ret = tileset_editor->edited_occlusion_shape;
- v = true;
- } else if (name == "tileset_script") {
- r_ret = tileset->get_script();
- v = true;
- }
- return v;
-}
-
-void TilesetEditorContext::_get_property_list(List<PropertyInfo> *p_list) const {
- if (snap_options_visible) {
- p_list->push_back(PropertyInfo(Variant::NIL, "Snap Options", PROPERTY_HINT_NONE, "options_", PROPERTY_USAGE_GROUP));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "options_offset"));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "options_step"));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "options_separation"));
- }
- if (tileset_editor->get_current_tile() >= 0 && !tileset.is_null()) {
- int id = tileset_editor->get_current_tile();
- p_list->push_back(PropertyInfo(Variant::NIL, "Selected Tile", PROPERTY_HINT_NONE, "tile_", PROPERTY_USAGE_GROUP));
- p_list->push_back(PropertyInfo(Variant::STRING, "tile_name"));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_tex_offset"));
- p_list->push_back(PropertyInfo(Variant::OBJECT, "tile_material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial"));
- p_list->push_back(PropertyInfo(Variant::COLOR, "tile_modulate"));
- p_list->push_back(PropertyInfo(Variant::INT, "tile_tile_mode", PROPERTY_HINT_ENUM, "SINGLE_TILE,AUTO_TILE,ATLAS_TILE"));
- if (tileset->tile_get_tile_mode(id) == TileSet::AUTO_TILE) {
- p_list->push_back(PropertyInfo(Variant::INT, "tile_autotile_bitmask_mode", PROPERTY_HINT_ENUM, "2X2,3X3 (minimal),3X3"));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_subtile_size"));
- p_list->push_back(PropertyInfo(Variant::INT, "tile_subtile_spacing", PROPERTY_HINT_RANGE, "0, 256, 1"));
- } else if (tileset->tile_get_tile_mode(id) == TileSet::ATLAS_TILE) {
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_subtile_size"));
- p_list->push_back(PropertyInfo(Variant::INT, "tile_subtile_spacing", PROPERTY_HINT_RANGE, "0, 256, 1"));
- }
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_occluder_offset"));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_navigation_offset"));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_shape_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_shape_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR));
- p_list->push_back(PropertyInfo(Variant::INT, "tile_z_index", PROPERTY_HINT_RANGE, itos(RS::CANVAS_ITEM_Z_MIN) + "," + itos(RS::CANVAS_ITEM_Z_MAX) + ",1"));
- }
- if (tileset_editor->edit_mode == TileSetEditor::EDITMODE_COLLISION && tileset_editor->edited_collision_shape.is_valid()) {
- p_list->push_back(PropertyInfo(Variant::OBJECT, "selected_collision", PROPERTY_HINT_RESOURCE_TYPE, tileset_editor->edited_collision_shape->get_class()));
- if (tileset_editor->edited_collision_shape.is_valid()) {
- p_list->push_back(PropertyInfo(Variant::BOOL, "selected_collision_one_way", PROPERTY_HINT_NONE));
- p_list->push_back(PropertyInfo(Variant::FLOAT, "selected_collision_one_way_margin", PROPERTY_HINT_NONE));
- }
- }
- if (tileset_editor->edit_mode == TileSetEditor::EDITMODE_NAVIGATION && tileset_editor->edited_navigation_shape.is_valid()) {
- p_list->push_back(PropertyInfo(Variant::OBJECT, "selected_navigation", PROPERTY_HINT_RESOURCE_TYPE, tileset_editor->edited_navigation_shape->get_class()));
- }
- if (tileset_editor->edit_mode == TileSetEditor::EDITMODE_OCCLUSION && tileset_editor->edited_occlusion_shape.is_valid()) {
- p_list->push_back(PropertyInfo(Variant::OBJECT, "selected_occlusion", PROPERTY_HINT_RESOURCE_TYPE, tileset_editor->edited_occlusion_shape->get_class()));
- }
- if (!tileset.is_null()) {
- p_list->push_back(PropertyInfo(Variant::OBJECT, "tileset_script", PROPERTY_HINT_RESOURCE_TYPE, "Script"));
- }
-}
-
-void TilesetEditorContext::_bind_methods() {
- ClassDB::bind_method("_hide_script_from_inspector", &TilesetEditorContext::_hide_script_from_inspector);
-}
-
-TilesetEditorContext::TilesetEditorContext(TileSetEditor *p_tileset_editor) {
- tileset_editor = p_tileset_editor;
- snap_options_visible = false;
-}
-
-void TileSetEditorPlugin::edit(Object *p_node) {
- if (Object::cast_to<TileSet>(p_node)) {
- tileset_editor->edit(Object::cast_to<TileSet>(p_node));
- }
-}
-
-bool TileSetEditorPlugin::handles(Object *p_node) const {
- return p_node->is_class("TileSet") || p_node->is_class("TilesetEditorContext");
-}
-
-void TileSetEditorPlugin::make_visible(bool p_visible) {
- if (p_visible) {
- tileset_editor_button->show();
- editor->make_bottom_panel_item_visible(tileset_editor);
- get_tree()->connect("idle_frame", Callable(tileset_editor, "_on_workspace_process"));
- } else {
- editor->hide_bottom_panel();
- tileset_editor_button->hide();
- get_tree()->disconnect("idle_frame", Callable(tileset_editor, "_on_workspace_process"));
- }
-}
-
-Dictionary TileSetEditorPlugin::get_state() const {
- Dictionary state;
- state["snap_offset"] = tileset_editor->snap_offset;
- state["snap_step"] = tileset_editor->snap_step;
- state["snap_separation"] = tileset_editor->snap_separation;
- state["snap_enabled"] = tileset_editor->tools[TileSetEditor::TOOL_GRID_SNAP]->is_pressed();
- state["keep_inside_tile"] = tileset_editor->tools[TileSetEditor::SHAPE_KEEP_INSIDE_TILE]->is_pressed();
- state["show_information"] = tileset_editor->tools[TileSetEditor::VISIBLE_INFO]->is_pressed();
- return state;
-}
-
-void TileSetEditorPlugin::set_state(const Dictionary &p_state) {
- Dictionary state = p_state;
- if (state.has("snap_step")) {
- tileset_editor->_set_snap_step(state["snap_step"]);
- }
-
- if (state.has("snap_offset")) {
- tileset_editor->_set_snap_off(state["snap_offset"]);
- }
-
- if (state.has("snap_separation")) {
- tileset_editor->_set_snap_sep(state["snap_separation"]);
- }
-
- if (state.has("snap_enabled")) {
- tileset_editor->tools[TileSetEditor::TOOL_GRID_SNAP]->set_pressed(state["snap_enabled"]);
- if (tileset_editor->helper) {
- tileset_editor->_on_grid_snap_toggled(state["snap_enabled"]);
- }
- }
-
- if (state.has("keep_inside_tile")) {
- tileset_editor->tools[TileSetEditor::SHAPE_KEEP_INSIDE_TILE]->set_pressed(state["keep_inside_tile"]);
- }
-
- if (state.has("show_information")) {
- tileset_editor->tools[TileSetEditor::VISIBLE_INFO]->set_pressed(state["show_information"]);
- }
-}
-
-TileSetEditorPlugin::TileSetEditorPlugin(EditorNode *p_node) {
- editor = p_node;
- tileset_editor = memnew(TileSetEditor(p_node));
-
- tileset_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE);
- tileset_editor->hide();
-
- tileset_editor_button = p_node->add_bottom_panel_item(TTR("TileSet"), tileset_editor);
- tileset_editor_button->hide();
-}
diff --git a/editor/plugins/tile_set_editor_plugin.h b/editor/plugins/tile_set_editor_plugin.h
deleted file mode 100644
index e778c18f44..0000000000
--- a/editor/plugins/tile_set_editor_plugin.h
+++ /dev/null
@@ -1,298 +0,0 @@
-/*************************************************************************/
-/* tile_set_editor_plugin.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_EDITOR_PLUGIN_H
-#define TILE_SET_EDITOR_PLUGIN_H
-
-#include "editor/editor_node.h"
-#include "scene/2d/sprite_2d.h"
-#include "scene/resources/concave_polygon_shape_2d.h"
-#include "scene/resources/convex_polygon_shape_2d.h"
-#include "scene/resources/tile_set.h"
-
-#define WORKSPACE_MARGIN Vector2(10, 10)
-class TilesetEditorContext;
-
-class TileSetEditor : public HSplitContainer {
- friend class TileSetEditorPlugin;
- friend class TilesetEditorContext;
-
- GDCLASS(TileSetEditor, HSplitContainer);
-
- enum TextureButtons {
- TOOL_TILESET_ADD_TEXTURE,
- TOOL_TILESET_REMOVE_TEXTURE,
- TOOL_TILESET_CREATE_SCENE,
- TOOL_TILESET_MERGE_SCENE,
- TOOL_TILESET_MAX
- };
-
- enum WorkspaceMode {
- WORKSPACE_EDIT,
- WORKSPACE_CREATE_SINGLE,
- WORKSPACE_CREATE_AUTOTILE,
- WORKSPACE_CREATE_ATLAS,
- WORKSPACE_MODE_MAX
- };
-
- enum EditMode {
- EDITMODE_REGION,
- EDITMODE_COLLISION,
- EDITMODE_OCCLUSION,
- EDITMODE_NAVIGATION,
- EDITMODE_BITMASK,
- EDITMODE_PRIORITY,
- EDITMODE_ICON,
- EDITMODE_Z_INDEX,
- EDITMODE_MAX
- };
-
- enum TileSetTools {
- SELECT_PREVIOUS,
- SELECT_NEXT,
- TOOL_SELECT,
- BITMASK_COPY,
- BITMASK_PASTE,
- BITMASK_CLEAR,
- SHAPE_NEW_POLYGON,
- SHAPE_NEW_RECTANGLE,
- SHAPE_TOGGLE_TYPE,
- SHAPE_DELETE,
- SHAPE_KEEP_INSIDE_TILE,
- TOOL_GRID_SNAP,
- ZOOM_OUT,
- ZOOM_1,
- ZOOM_IN,
- VISIBLE_INFO,
- TOOL_MAX
- };
-
- struct SubtileData {
- Array collisions;
- Ref<OccluderPolygon2D> occlusion_shape;
- Ref<NavigationPolygon> navigation_shape;
- };
-
- Ref<TileSet> tileset;
- TilesetEditorContext *helper;
- EditorNode *editor;
- UndoRedo *undo_redo;
-
- ConfirmationDialog *cd;
- AcceptDialog *err_dialog;
- EditorFileDialog *texture_dialog;
-
- ItemList *texture_list;
- int option;
- Button *tileset_toolbar_buttons[TOOL_TILESET_MAX];
- MenuButton *tileset_toolbar_tools;
- Map<RID, Ref<Texture2D>> texture_map;
-
- bool creating_shape;
- int dragging_point;
- bool tile_names_visible;
- Vector2 region_from;
- Rect2 edited_region;
- bool draw_edited_region;
- Vector2 edited_shape_coord;
- PackedVector2Array current_shape;
- Map<Vector2i, SubtileData> current_tile_data;
- Map<Vector2, uint32_t> bitmask_map_copy;
-
- Vector2 snap_step;
- Vector2 snap_offset;
- Vector2 snap_separation;
-
- Ref<Shape2D> edited_collision_shape;
- Ref<OccluderPolygon2D> edited_occlusion_shape;
- Ref<NavigationPolygon> edited_navigation_shape;
-
- int current_item_index;
- Sprite2D *preview;
- ScrollContainer *scroll;
- Label *empty_message;
- Control *workspace_container;
- bool draw_handles;
- Control *workspace_overlay;
- Control *workspace;
- Button *tool_workspacemode[WORKSPACE_MODE_MAX];
- Button *tool_editmode[EDITMODE_MAX];
- HSeparator *separator_editmode;
- HBoxContainer *toolbar;
- Button *tools[TOOL_MAX];
- VSeparator *separator_shape_toggle;
- VSeparator *separator_bitmask;
- VSeparator *separator_delete;
- VSeparator *separator_grid;
- SpinBox *spin_priority;
- SpinBox *spin_z_index;
- WorkspaceMode workspace_mode;
- EditMode edit_mode;
- int current_tile;
-
- float max_scale;
- float min_scale;
- float scale_ratio;
-
- void update_texture_list();
- void update_texture_list_icon();
-
- void add_texture(Ref<Texture2D> p_texture);
- void remove_texture(Ref<Texture2D> p_texture);
-
- Ref<Texture2D> get_current_texture();
-
- static void _import_node(Node *p_node, Ref<TileSet> p_library);
- static void _import_scene(Node *p_scene, Ref<TileSet> p_library, bool p_merge);
- void _undo_redo_import_scene(Node *p_scene, bool p_merge);
-
- bool _is_drop_valid(const Dictionary &p_drag_data, const Dictionary &p_item_data) const;
- Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
- bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
- void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
- void _file_load_request(const Vector<String> &p_path, int p_at_pos = -1);
-
-protected:
- static void _bind_methods();
- void _notification(int p_what);
-
-public:
- void edit(const Ref<TileSet> &p_tileset);
- static Error update_library_file(Node *p_base_scene, Ref<TileSet> ml, bool p_merge = true);
-
- TileSetEditor(EditorNode *p_editor);
- ~TileSetEditor();
-
-private:
- void _on_tileset_toolbar_button_pressed(int p_index);
- void _on_tileset_toolbar_confirm();
- void _on_texture_list_selected(int p_index);
- void _on_textures_added(const PackedStringArray &p_paths);
- void _on_edit_mode_changed(int p_edit_mode);
- void _on_workspace_mode_changed(int p_workspace_mode);
- void _on_workspace_overlay_draw();
- void _on_workspace_draw();
- void _on_workspace_process();
- void _on_scroll_container_input(const Ref<InputEvent> &p_event);
- void _on_workspace_input(const Ref<InputEvent> &p_ie);
- void _on_tool_clicked(int p_tool);
- void _on_priority_changed(float val);
- void _on_z_index_changed(float val);
- void _on_grid_snap_toggled(bool p_val);
- Vector<Vector2> _get_collision_shape_points(const Ref<Shape2D> &p_shape);
- Vector<Vector2> _get_edited_shape_points();
- void _set_edited_shape_points(const Vector<Vector2> &points);
- void _update_tile_data();
- void _update_toggle_shape_button();
- void _select_next_tile();
- void _select_previous_tile();
- Array _get_tiles_in_current_texture(bool sorted = false);
- bool _sort_tiles(Variant p_a, Variant p_b);
- void _select_next_subtile();
- void _select_previous_subtile();
- void _select_next_shape();
- void _select_previous_shape();
- void _set_edited_collision_shape(const Ref<Shape2D> &p_shape);
- void _set_snap_step(Vector2 p_val);
- void _set_snap_off(Vector2 p_val);
- void _set_snap_sep(Vector2 p_val);
-
- void _validate_current_tile_id();
- void _select_edited_shape_coord();
- void _undo_tile_removal(int p_id);
-
- void _zoom_in();
- void _zoom_out();
- void _zoom_reset();
-
- void draw_highlight_current_tile();
- void draw_highlight_subtile(Vector2 coord, const Vector<Vector2> &other_highlighted = Vector<Vector2>());
- void draw_tile_subdivision(int p_id, Color p_color) const;
- void draw_edited_region_subdivision() const;
- void draw_grid_snap();
- void draw_polygon_shapes();
- void close_shape(const Vector2 &shape_anchor);
- void select_coord(const Vector2 &coord);
- Vector2 snap_point(const Vector2 &point);
- void update_workspace_tile_mode();
- void update_workspace_minsize();
- void update_edited_region(const Vector2 &end_point);
- int get_grabbed_point(const Vector2 &p_mouse_pos, real_t grab_threshold);
- bool is_within_grabbing_distance_of_first_point(const Vector2 &p_pos, real_t p_grab_threshold);
-
- int get_current_tile() const;
- void set_current_tile(int p_id);
-};
-
-class TilesetEditorContext : public Object {
- friend class TileSetEditor;
- GDCLASS(TilesetEditorContext, Object);
-
- Ref<TileSet> tileset;
- TileSetEditor *tileset_editor;
- bool snap_options_visible;
-
-public:
- bool _hide_script_from_inspector() { return true; }
- void set_tileset(const Ref<TileSet> &p_tileset);
-
-private:
- void set_snap_options_visible(bool p_visible);
-
-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:
- TilesetEditorContext(TileSetEditor *p_tileset_editor);
-};
-
-class TileSetEditorPlugin : public EditorPlugin {
- GDCLASS(TileSetEditorPlugin, EditorPlugin);
-
- TileSetEditor *tileset_editor;
- Button *tileset_editor_button;
- EditorNode *editor;
-
-public:
- virtual String get_name() const override { return "TileSet"; }
- bool has_main_screen() const override { return false; }
- virtual void edit(Object *p_node) override;
- virtual bool handles(Object *p_node) const override;
- virtual void make_visible(bool p_visible) override;
- void set_state(const Dictionary &p_state) override;
- Dictionary get_state() const override;
-
- TileSetEditorPlugin(EditorNode *p_node);
-};
-
-#endif // TILE_SET_EDITOR_PLUGIN_H
diff --git a/editor/plugins/tiles/SCsub b/editor/plugins/tiles/SCsub
new file mode 100644
index 0000000000..359d04e5df
--- /dev/null
+++ b/editor/plugins/tiles/SCsub
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+Import("env")
+
+env.add_source_files(env.editor_sources, "*.cpp")
diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp
new file mode 100644
index 0000000000..4f7eabac24
--- /dev/null
+++ b/editor/plugins/tiles/tile_atlas_view.cpp
@@ -0,0 +1,649 @@
+/*************************************************************************/
+/* tile_atlas_view.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_atlas_view.h"
+
+#include "core/input/input.h"
+#include "core/os/keyboard.h"
+#include "scene/gui/box_container.h"
+#include "scene/gui/center_container.h"
+#include "scene/gui/label.h"
+#include "scene/gui/panel.h"
+#include "scene/gui/texture_rect.h"
+
+#include "editor/editor_scale.h"
+
+void TileAtlasView::_gui_input(const Ref<InputEvent> &p_event) {
+ bool ctrl = Input::get_singleton()->is_key_pressed(KEY_CONTROL);
+
+ Ref<InputEventMouseButton> b = p_event;
+ if (b.is_valid()) {
+ if (ctrl && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) {
+ // Zoom out
+ zoom_widget->set_zoom_by_increments(-2);
+ emit_signal("transform_changed", zoom_widget->get_zoom(), Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll()));
+ _update_zoom(zoom_widget->get_zoom(), true);
+ accept_event();
+ }
+
+ if (ctrl && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_UP) {
+ // Zoom in
+ zoom_widget->set_zoom_by_increments(2);
+ emit_signal("transform_changed", zoom_widget->get_zoom(), Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll()));
+ _update_zoom(zoom_widget->get_zoom(), true);
+ accept_event();
+ }
+ }
+}
+
+Size2i TileAtlasView::_compute_base_tiles_control_size() {
+ // Update the texture.
+ Vector2i size;
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+ if (texture.is_valid()) {
+ size = texture->get_size();
+ }
+
+ // Extend the size to all existing tiles.
+ Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
+ grid_size = grid_size.max(tile_id + Vector2i(1, 1));
+ }
+ size = size.max(grid_size * (tile_set_atlas_source->get_texture_region_size() + tile_set_atlas_source->get_separation()) + tile_set_atlas_source->get_margins());
+
+ return size;
+}
+
+Size2i TileAtlasView::_compute_alternative_tiles_control_size() {
+ Vector2i size;
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
+ int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(tile_id);
+ Vector2i line_size;
+ Size2i texture_region_size = tile_set_atlas_source->get_tile_texture_region(tile_id).size;
+ for (int j = 1; j < alternatives_count; j++) {
+ int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j);
+ bool transposed = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(tile_id, alternative_id))->get_transpose();
+ line_size.x += transposed ? texture_region_size.y : texture_region_size.x;
+ line_size.y = MAX(line_size.y, transposed ? texture_region_size.x : texture_region_size.y);
+ }
+ size.x = MAX(size.x, line_size.x);
+ size.y += line_size.y;
+ }
+
+ return size;
+}
+
+void TileAtlasView::_update_zoom(float p_zoom, bool p_zoom_on_mouse_pos, Vector2i p_scroll) {
+ // Compute the minimum sizes.
+ Size2i base_tiles_control_size = _compute_base_tiles_control_size();
+ base_tiles_root_control->set_custom_minimum_size(Vector2(base_tiles_control_size) * p_zoom);
+
+ Size2i alternative_tiles_control_size = _compute_alternative_tiles_control_size();
+ alternative_tiles_root_control->set_custom_minimum_size(Vector2(alternative_tiles_control_size) * p_zoom);
+
+ // Set the texture for the base tiles.
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+
+ // Set the scales.
+ if (base_tiles_control_size.x > 0 && base_tiles_control_size.y > 0) {
+ base_tiles_drawing_root->set_scale(Vector2(p_zoom, p_zoom));
+ } else {
+ base_tiles_drawing_root->set_scale(Vector2(1, 1));
+ }
+ if (alternative_tiles_control_size.x > 0 && alternative_tiles_control_size.y > 0) {
+ alternative_tiles_drawing_root->set_scale(Vector2(p_zoom, p_zoom));
+ } else {
+ alternative_tiles_drawing_root->set_scale(Vector2(1, 1));
+ }
+
+ // Update the margin container's margins.
+ const char *constants[] = { "margin_left", "margin_top", "margin_right", "margin_bottom" };
+ for (int i = 0; i < 4; i++) {
+ margin_container->add_theme_constant_override(constants[i], margin_container_paddings[i] * p_zoom);
+ }
+
+ // Update the backgrounds.
+ background_left->update();
+ background_right->update();
+
+ if (p_scroll != Vector2i(-1, -1)) {
+ scroll_container->set_h_scroll(p_scroll.x);
+ scroll_container->set_v_scroll(p_scroll.y);
+ }
+
+ // Zoom on the position.
+ if (previous_zoom != p_zoom) {
+ // TODO: solve this.
+ // There is however an issue with scrollcainter preventing this, as it seems
+ // that the scrollbars are not updated right aways after its children update.
+
+ // Compute point on previous area.
+ /*Vector2 max = Vector2(scroll_container->get_h_scrollbar()->get_max(), scroll_container->get_v_scrollbar()->get_max());
+ Vector2 min = Vector2(scroll_container->get_h_scrollbar()->get_min(), scroll_container->get_v_scrollbar()->get_min());
+ Vector2 value = Vector2(scroll_container->get_h_scrollbar()->get_value(), scroll_container->get_v_scrollbar()->get_value());
+
+ Vector2 old_max = max * previous_zoom / p_zoom;
+
+ Vector2 max_pixel_change = max - old_max;
+ Vector2 ratio = ((value + scroll_container->get_local_mouse_position()) / old_max).max(Vector2()).min(Vector2(1,1));
+ Vector2 offset = max_pixel_change * ratio;
+
+ print_line("--- ZOOMED ---");
+ print_line(vformat("max: %s", max));
+ print_line(vformat("min: %s", min));
+ print_line(vformat("value: %s", value));
+ print_line(vformat("size: %s", scroll_container->get_size()));
+ print_line(vformat("mouse_pos: %s", scroll_container->get_local_mouse_position()));
+
+ print_line(vformat("ratio: %s", ratio));
+ print_line(vformat("max_pixel_change: %s", max_pixel_change));
+ print_line(vformat("offset: %s", offset));
+
+
+ print_line(vformat("value before: %s", Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll())));
+ scroll_container->set_h_scroll(10000);//scroll_container->get_h_scroll()+offset.x);
+ scroll_container->set_v_scroll(10000);//scroll_container->get_v_scroll()+offset.y);
+ print_line(vformat("value after: %s", Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll())));
+ */
+
+ previous_zoom = p_zoom;
+ }
+}
+
+void TileAtlasView::_scroll_changed() {
+ emit_signal("transform_changed", zoom_widget->get_zoom(), Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll()));
+}
+
+void TileAtlasView::_zoom_widget_changed() {
+ _update_zoom(zoom_widget->get_zoom());
+ emit_signal("transform_changed", zoom_widget->get_zoom(), Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll()));
+}
+
+void TileAtlasView::_base_tiles_root_control_gui_input(const Ref<InputEvent> &p_event) {
+ base_tiles_root_control->set_tooltip("");
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ 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) {
+ coords = tile_set_atlas_source->get_tile_at_coords(coords);
+ if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+ base_tiles_root_control->set_tooltip(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: 0"), source_id, coords));
+ }
+ }
+ }
+}
+
+void TileAtlasView::_draw_base_tiles() {
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+ if (texture.is_valid()) {
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
+
+ // Draw the texture, square by square.
+ Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
+ 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) {
+ Rect2i rect = Rect2i(texture_region_size * coords + margins, texture_region_size);
+ base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
+ }
+ }
+ }
+
+ // Draw the texture around the grid.
+ Rect2i rect;
+ // Top.
+ rect.position = Vector2i();
+ 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);
+ // 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);
+
+ // Draw actual tiles, using their properties (modulation, etc...)
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i);
+
+ // Update the y to max value.
+ 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);
+ }
+ }
+}
+
+void TileAtlasView::_draw_base_tiles_texture_grid() {
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+ if (texture.is_valid()) {
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i separation = tile_set_atlas_source->get_separation();
+ Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
+
+ Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
+
+ // Draw each tile texture region.
+ for (int x = 0; x < grid_size.x; x++) {
+ 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 == Vector2i(x, y)) {
+ // Draw existing tile.
+ Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(base_tile_coords);
+ Vector2 region_size = texture_region_size * size_in_atlas + separation * (size_in_atlas - Vector2i(1, 1));
+ base_tiles_texture_grid->draw_rect(Rect2i(origin, region_size), Color(1.0, 1.0, 1.0, 0.8), false);
+ }
+ } else {
+ // Draw the grid.
+ base_tiles_texture_grid->draw_rect(Rect2i(origin, texture_region_size), Color(0.7, 0.7, 0.7, 0.1), false);
+ }
+ }
+ }
+ }
+}
+
+void TileAtlasView::_draw_base_tiles_dark() {
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+ if (texture.is_valid()) {
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i separation = tile_set_atlas_source->get_separation();
+ Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
+
+ Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
+
+ // Draw each tile texture region.
+ for (int x = 0; x < grid_size.x; x++) {
+ 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) {
+ // Draw the grid.
+ base_tiles_dark->draw_rect(Rect2i(origin, texture_region_size), Color(0.0, 0.0, 0.0, 0.5), true);
+ }
+ }
+ }
+ }
+}
+
+void TileAtlasView::_draw_base_tiles_shape_grid() {
+ // Draw the shapes.
+ Vector2i tile_shape_size = tile_set->get_tile_size();
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
+ Vector2 in_tile_base_offset = tile_set_atlas_source->get_tile_effective_texture_offset(tile_id, 0);
+ Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(tile_id);
+ Vector2 origin = texture_region.position + (texture_region.size - tile_shape_size) / 2 + in_tile_base_offset;
+
+ // Draw only if the tile shape fits in the texture region
+ tile_set->draw_tile_shape(base_tiles_shape_grid, Rect2(origin, tile_shape_size), Color(1.0, 0.5, 0.2, 0.8));
+ }
+}
+
+void TileAtlasView::_alternative_tiles_root_control_gui_input(const Ref<InputEvent> &p_event) {
+ alternative_tiles_root_control->set_tooltip("");
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ Transform2D xform = alternative_tiles_drawing_root->get_transform().affine_inverse();
+ 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) {
+ alternative_tiles_root_control->set_tooltip(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: %d"), source_id, coords, alternative_id));
+ }
+ }
+}
+
+void TileAtlasView::_draw_alternatives() {
+ // Draw the alternative tiles.
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+ if (texture.is_valid()) {
+ Vector2 current_pos;
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i);
+ current_pos.x = 0;
+ int y_increment = 0;
+ Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(atlas_coords);
+ int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(atlas_coords);
+ for (int j = 1; j < alternatives_count; j++) {
+ int alternative_id = tile_set_atlas_source->get_alternative_tile_id(atlas_coords, j);
+ TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(atlas_coords, alternative_id));
+ bool transposed = tile_data->get_transpose();
+
+ // Update the y to max value.
+ Vector2i offset_pos = current_pos;
+ if (transposed) {
+ offset_pos = (current_pos + Vector2(texture_region.size.y, texture_region.size.x) / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id));
+ y_increment = MAX(y_increment, texture_region.size.x);
+ } else {
+ offset_pos = (current_pos + texture_region.size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id));
+ y_increment = MAX(y_increment, texture_region.size.y);
+ }
+
+ // Draw the tile.
+ TileSetAtlasPluginRendering::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;
+ }
+ if (alternatives_count > 1) {
+ current_pos.y += y_increment;
+ }
+ }
+ }
+}
+
+void TileAtlasView::_draw_background_left() {
+ Ref<Texture2D> texture = get_theme_icon("Checkerboard", "EditorIcons");
+ background_left->set_size(base_tiles_root_control->get_custom_minimum_size());
+ background_left->draw_texture_rect(texture, Rect2(Vector2(), background_left->get_size()), true);
+}
+
+void TileAtlasView::_draw_background_right() {
+ Ref<Texture2D> texture = get_theme_icon("Checkerboard", "EditorIcons");
+ background_right->set_size(alternative_tiles_root_control->get_custom_minimum_size());
+ background_right->draw_texture_rect(texture, Rect2(Vector2(), background_right->get_size()), true);
+}
+
+void TileAtlasView::set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id) {
+ ERR_FAIL_COND(!p_tile_set);
+ ERR_FAIL_COND(!p_tile_set_atlas_source);
+ ERR_FAIL_COND(p_source_id < 0);
+ ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source);
+
+ tile_set = p_tile_set;
+ tile_set_atlas_source = p_tile_set_atlas_source;
+ source_id = p_source_id;
+
+ // Show or hide the view.
+ bool valid = tile_set_atlas_source->get_texture().is_valid();
+ hbox->set_visible(valid);
+ missing_source_label->set_visible(!valid);
+
+ // Update the rect cache.
+ _update_alternative_tiles_rect_cache();
+
+ // Update everything.
+ _update_zoom(zoom_widget->get_zoom());
+
+ // Change children control size.
+ Size2i base_tiles_control_size = _compute_base_tiles_control_size();
+ for (int i = 0; i < base_tiles_drawing_root->get_child_count(); i++) {
+ Control *control = Object::cast_to<Control>(base_tiles_drawing_root->get_child(i));
+ if (control) {
+ control->set_size(base_tiles_control_size);
+ }
+ }
+
+ Size2i alternative_control_size = _compute_alternative_tiles_control_size();
+ for (int i = 0; i < alternative_tiles_drawing_root->get_child_count(); i++) {
+ Control *control = Object::cast_to<Control>(alternative_tiles_drawing_root->get_child(i));
+ if (control) {
+ control->set_size(alternative_control_size);
+ }
+ }
+
+ // Update.
+ base_tiles_draw->update();
+ base_tiles_texture_grid->update();
+ base_tiles_shape_grid->update();
+ base_tiles_dark->update();
+ alternatives_draw->update();
+ background_left->update();
+ background_right->update();
+}
+
+float TileAtlasView::get_zoom() const {
+ return zoom_widget->get_zoom();
+};
+
+void TileAtlasView::set_transform(float p_zoom, Vector2i p_scroll) {
+ zoom_widget->set_zoom(p_zoom);
+ _update_zoom(zoom_widget->get_zoom(), false, p_scroll);
+};
+
+void TileAtlasView::set_padding(Side p_side, int p_padding) {
+ ERR_FAIL_COND(p_padding < 0);
+ margin_container_paddings[p_side] = p_padding;
+}
+
+Vector2i TileAtlasView::get_atlas_tile_coords_at_pos(const Vector2 p_pos) const {
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+ if (texture.is_valid()) {
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i separation = tile_set_atlas_source->get_separation();
+ Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
+
+ // Compute index in atlas
+ Vector2 pos = p_pos - margins;
+ Vector2i ret = (pos / (texture_region_size + separation)).floor();
+
+ return ret;
+ }
+
+ return TileSetAtlasSource::INVALID_ATLAS_COORDS;
+}
+
+void TileAtlasView::_update_alternative_tiles_rect_cache() {
+ alternative_tiles_rect_cache.clear();
+
+ Rect2i current;
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
+ int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(tile_id);
+ Size2i texture_region_size = tile_set_atlas_source->get_tile_texture_region(tile_id).size;
+ int line_height = 0;
+ for (int j = 1; j < alternatives_count; j++) {
+ int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j);
+ TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(tile_id, alternative_id));
+ bool transposed = tile_data->get_transpose();
+ current.size = transposed ? Vector2i(texture_region_size.y, texture_region_size.x) : texture_region_size;
+
+ // Update the rect.
+ if (!alternative_tiles_rect_cache.has(tile_id)) {
+ alternative_tiles_rect_cache[tile_id] = Map<int, Rect2i>();
+ }
+ alternative_tiles_rect_cache[tile_id][alternative_id] = current;
+
+ current.position.x += transposed ? texture_region_size.y : texture_region_size.x;
+ line_height = MAX(line_height, transposed ? texture_region_size.x : texture_region_size.y);
+ }
+
+ current.position.x = 0;
+ current.position.y += line_height;
+ }
+}
+
+Vector3i TileAtlasView::get_alternative_tile_at_pos(const Vector2 p_pos) const {
+ for (Map<Vector2, Map<int, Rect2i>>::Element *E_coords = alternative_tiles_rect_cache.front(); E_coords; E_coords = E_coords->next()) {
+ for (Map<int, Rect2i>::Element *E_alternative = E_coords->value().front(); E_alternative; E_alternative = E_alternative->next()) {
+ if (E_alternative->value().has_point(p_pos)) {
+ return Vector3i(E_coords->key().x, E_coords->key().y, E_alternative->key());
+ }
+ }
+ }
+
+ return Vector3i(TileSetAtlasSource::INVALID_ATLAS_COORDS.x, TileSetAtlasSource::INVALID_ATLAS_COORDS.y, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
+}
+
+Rect2i TileAtlasView::get_alternative_tile_rect(const Vector2i p_coords, int p_alternative_tile) {
+ ERR_FAIL_COND_V_MSG(!alternative_tiles_rect_cache.has(p_coords), Rect2i(), vformat("No cached rect for tile coords:%s", p_coords));
+ ERR_FAIL_COND_V_MSG(!alternative_tiles_rect_cache[p_coords].has(p_alternative_tile), Rect2i(), vformat("No cached rect for tile coords:%s alternative_id:%d", p_coords, p_alternative_tile));
+
+ return alternative_tiles_rect_cache[p_coords][p_alternative_tile];
+}
+
+void TileAtlasView::update() {
+ scroll_container->update();
+ base_tiles_texture_grid->update();
+ base_tiles_shape_grid->update();
+ base_tiles_dark->update();
+ alternatives_draw->update();
+ background_left->update();
+ background_right->update();
+}
+
+void TileAtlasView::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("transform_changed", PropertyInfo(Variant::FLOAT, "zoom"), PropertyInfo(Variant::VECTOR2, "scroll")));
+}
+
+TileAtlasView::TileAtlasView() {
+ Panel *panel_container = memnew(Panel);
+ panel_container->set_h_size_flags(SIZE_EXPAND_FILL);
+ panel_container->set_v_size_flags(SIZE_EXPAND_FILL);
+ panel_container->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ add_child(panel_container);
+
+ //Scrolling
+ scroll_container = memnew(ScrollContainer);
+ scroll_container->get_h_scrollbar()->connect("value_changed", callable_mp(this, &TileAtlasView::_scroll_changed).unbind(1));
+ scroll_container->get_v_scrollbar()->connect("value_changed", callable_mp(this, &TileAtlasView::_scroll_changed).unbind(1));
+ panel_container->add_child(scroll_container);
+ scroll_container->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+
+ zoom_widget = memnew(EditorZoomWidget);
+ add_child(zoom_widget);
+ zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE);
+ zoom_widget->connect("zoom_changed", callable_mp(this, &TileAtlasView::_zoom_widget_changed).unbind(1));
+
+ CenterContainer *center_container = memnew(CenterContainer);
+ center_container->set_h_size_flags(SIZE_EXPAND_FILL);
+ center_container->set_v_size_flags(SIZE_EXPAND_FILL);
+ center_container->connect("gui_input", callable_mp(this, &TileAtlasView::_gui_input));
+ scroll_container->add_child(center_container);
+
+ missing_source_label = memnew(Label);
+ missing_source_label->set_text(TTR("No atlas source with a valid texture selected."));
+ center_container->add_child(missing_source_label);
+
+ margin_container = memnew(MarginContainer);
+ center_container->add_child(margin_container);
+
+ hbox = memnew(HBoxContainer);
+ hbox->add_theme_constant_override("separation", 10);
+ hbox->hide();
+ margin_container->add_child(hbox);
+
+ VBoxContainer *left_vbox = memnew(VBoxContainer);
+ hbox->add_child(left_vbox);
+
+ VBoxContainer *right_vbox = memnew(VBoxContainer);
+ hbox->add_child(right_vbox);
+
+ // Base tiles.
+ Label *base_tile_label = memnew(Label);
+ base_tile_label->set_text(TTR("Base Tiles"));
+ base_tile_label->set_align(Label::ALIGN_CENTER);
+ left_vbox->add_child(base_tile_label);
+
+ base_tiles_root_control = memnew(Control);
+ base_tiles_root_control->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ base_tiles_root_control->connect("gui_input", callable_mp(this, &TileAtlasView::_base_tiles_root_control_gui_input));
+ left_vbox->add_child(base_tiles_root_control);
+
+ background_left = memnew(Control);
+ background_left->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ background_left->set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED);
+ background_left->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ background_left->connect("draw", callable_mp(this, &TileAtlasView::_draw_background_left));
+ base_tiles_root_control->add_child(background_left);
+
+ base_tiles_drawing_root = memnew(Control);
+ base_tiles_drawing_root->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ base_tiles_drawing_root->set_texture_filter(TEXTURE_FILTER_NEAREST);
+ base_tiles_drawing_root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ base_tiles_root_control->add_child(base_tiles_drawing_root);
+
+ base_tiles_draw = memnew(Control);
+ base_tiles_draw->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ base_tiles_draw->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles));
+ base_tiles_draw->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ base_tiles_drawing_root->add_child(base_tiles_draw);
+
+ base_tiles_texture_grid = memnew(Control);
+ base_tiles_texture_grid->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ base_tiles_texture_grid->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_texture_grid));
+ base_tiles_texture_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ base_tiles_drawing_root->add_child(base_tiles_texture_grid);
+
+ base_tiles_shape_grid = memnew(Control);
+ base_tiles_shape_grid->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ base_tiles_shape_grid->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_shape_grid));
+ base_tiles_shape_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ base_tiles_drawing_root->add_child(base_tiles_shape_grid);
+
+ base_tiles_dark = memnew(Control);
+ base_tiles_dark->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ base_tiles_dark->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_dark));
+ base_tiles_dark->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ base_tiles_drawing_root->add_child(base_tiles_dark);
+
+ // Alternative tiles.
+ Label *alternative_tiles_label = memnew(Label);
+ alternative_tiles_label->set_text(TTR("Alternative Tiles"));
+ alternative_tiles_label->set_align(Label::ALIGN_CENTER);
+ right_vbox->add_child(alternative_tiles_label);
+
+ alternative_tiles_root_control = memnew(Control);
+ alternative_tiles_root_control->connect("gui_input", callable_mp(this, &TileAtlasView::_alternative_tiles_root_control_gui_input));
+ right_vbox->add_child(alternative_tiles_root_control);
+
+ background_right = memnew(Control);
+ background_right->set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED);
+ background_right->connect("draw", callable_mp(this, &TileAtlasView::_draw_background_right));
+ background_right->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ alternative_tiles_root_control->add_child(background_right);
+
+ alternative_tiles_drawing_root = memnew(Control);
+ alternative_tiles_drawing_root->set_texture_filter(TEXTURE_FILTER_NEAREST);
+ alternative_tiles_drawing_root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ alternative_tiles_root_control->add_child(alternative_tiles_drawing_root);
+
+ alternatives_draw = memnew(Control);
+ alternatives_draw->connect("draw", callable_mp(this, &TileAtlasView::_draw_alternatives));
+ alternatives_draw->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ alternative_tiles_drawing_root->add_child(alternatives_draw);
+}
diff --git a/editor/plugins/tiles/tile_atlas_view.h b/editor/plugins/tiles/tile_atlas_view.h
new file mode 100644
index 0000000000..28fd3ed1e0
--- /dev/null
+++ b/editor/plugins/tiles/tile_atlas_view.h
@@ -0,0 +1,153 @@
+/*************************************************************************/
+/* tile_atlas_view.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_ATLAS_VIEW_H
+#define TILE_ATLAS_VIEW_H
+
+#include "editor/editor_zoom_widget.h"
+#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+#include "scene/gui/label.h"
+#include "scene/gui/margin_container.h"
+#include "scene/gui/scroll_container.h"
+#include "scene/gui/texture_rect.h"
+#include "scene/resources/tile_set.h"
+
+class TileAtlasView : public Control {
+ GDCLASS(TileAtlasView, Control);
+
+private:
+ TileSet *tile_set;
+ TileSetAtlasSource *tile_set_atlas_source;
+ int source_id = -1;
+
+ float previous_zoom = 1.0;
+ EditorZoomWidget *zoom_widget;
+ void _zoom_widget_changed();
+ void _scroll_changed();
+ void _update_zoom(float p_zoom, bool p_zoom_on_mouse_pos = false, Vector2i p_scroll = Vector2i(-1, -1));
+ void _gui_input(const Ref<InputEvent> &p_event);
+
+ Map<Vector2, Map<int, Rect2i>> alternative_tiles_rect_cache;
+ void _update_alternative_tiles_rect_cache();
+
+ ScrollContainer *scroll_container;
+ MarginContainer *margin_container;
+ int margin_container_paddings[4] = { 0, 0, 0, 0 };
+ HBoxContainer *hbox;
+ Label *missing_source_label;
+
+ // Background
+ Control *background_left;
+ void _draw_background_left();
+ Control *background_right;
+ void _draw_background_right();
+
+ // Left side.
+ Control *base_tiles_root_control;
+ void _base_tiles_root_control_gui_input(const Ref<InputEvent> &p_event);
+
+ Control *base_tiles_drawing_root;
+
+ Control *base_tiles_draw;
+ void _draw_base_tiles();
+
+ Control *base_tiles_texture_grid;
+ void _draw_base_tiles_texture_grid();
+
+ Control *base_tiles_shape_grid;
+ void _draw_base_tiles_shape_grid();
+
+ Control *base_tiles_dark;
+ void _draw_base_tiles_dark();
+
+ Size2i _compute_base_tiles_control_size();
+
+ // Right side.
+ Control *alternative_tiles_root_control;
+ void _alternative_tiles_root_control_gui_input(const Ref<InputEvent> &p_event);
+
+ Control *alternative_tiles_drawing_root;
+
+ Control *alternatives_draw;
+ void _draw_alternatives();
+
+ Size2i _compute_alternative_tiles_control_size();
+
+protected:
+ static void _bind_methods();
+
+public:
+ // Global.
+ void set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id);
+
+ ScrollContainer *get_scroll_container() { return scroll_container; };
+
+ float get_zoom() const;
+ void set_transform(float p_zoom, Vector2i p_scroll);
+
+ void set_padding(Side p_side, int p_padding);
+
+ // Left side.
+ void set_texture_grid_visible(bool p_visible) { base_tiles_texture_grid->set_visible(p_visible); };
+ void set_dark_visible(bool p_visible) { base_tiles_dark->set_visible(p_visible); };
+ void set_tile_shape_grid_visible(bool p_visible) { base_tiles_shape_grid->set_visible(p_visible); };
+
+ Vector2i get_atlas_tile_coords_at_pos(const Vector2 p_pos) const;
+
+ void add_control_over_atlas_tiles(Control *p_control, bool scaled = true) {
+ if (scaled) {
+ base_tiles_drawing_root->add_child(p_control);
+ } else {
+ base_tiles_root_control->add_child(p_control);
+ }
+ p_control->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+ };
+
+ // Right side.
+ Vector3i get_alternative_tile_at_pos(const Vector2 p_pos) const;
+ Rect2i get_alternative_tile_rect(const Vector2i p_coords, int p_alternative_tile);
+
+ void add_control_over_alternative_tiles(Control *p_control, bool scaled = true) {
+ if (scaled) {
+ alternative_tiles_drawing_root->add_child(p_control);
+ } else {
+ alternative_tiles_root_control->add_child(p_control);
+ }
+ p_control->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+ };
+
+ // Update everything.
+ void update();
+
+ TileAtlasView();
+};
+
+#endif // TILE_ATLAS_VIEW
diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp
new file mode 100644
index 0000000000..0b674bec39
--- /dev/null
+++ b/editor/plugins/tiles/tile_data_editors.cpp
@@ -0,0 +1,218 @@
+/*************************************************************************/
+/* tile_data_editors.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_data_editors.h"
+
+#include "tile_set_editor.h"
+
+TileData *TileDataEditor::_get_tile_data(TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile) {
+ ERR_FAIL_COND_V(!p_tile_set, nullptr);
+ ERR_FAIL_COND_V(!p_tile_set->has_source(p_atlas_source_id), nullptr);
+
+ TileData *td = nullptr;
+ TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id);
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ ERR_FAIL_COND_V(!atlas_source->has_tile(p_atlas_coords), nullptr);
+ ERR_FAIL_COND_V(!atlas_source->has_alternative_tile(p_atlas_coords, p_alternative_tile), nullptr);
+ td = Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile));
+ }
+
+ return td;
+}
+
+void TileDataEditor::edit(TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) {
+}
+
+void TileDataTextureOffsetEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) {
+ TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile);
+ ERR_FAIL_COND(!tile_data);
+
+ bool valid;
+ Variant value = tile_data->get(p_property, &valid);
+ if (!valid) {
+ return;
+ }
+ ERR_FAIL_COND(value.get_type() != Variant::VECTOR2I);
+
+ Vector2i tile_set_tile_size = p_tile_set->get_tile_size();
+ Rect2i rect = Rect2i(-tile_set_tile_size / 2, tile_set_tile_size);
+ p_tile_set->draw_tile_shape(p_canvas_item, p_transform.xform(rect), Color(1.0, 0.0, 0.0));
+}
+
+void TileDataIntegerEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) {
+ TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile);
+ ERR_FAIL_COND(!tile_data);
+
+ bool valid;
+ Variant value = tile_data->get(p_property, &valid);
+ if (!valid) {
+ return;
+ }
+ ERR_FAIL_COND(value.get_type() != Variant::INT);
+
+ Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font("bold", "EditorFonts");
+ int height = font->get_height();
+ int width = 200;
+ p_canvas_item->draw_string(font, p_transform.get_origin() + Vector2i(-width / 2, height / 2), vformat("%d", value), HALIGN_CENTER, width, -1, Color(1, 1, 1), 1, Color(0, 0, 0, 1));
+}
+
+void TileDataFloatEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) {
+ TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile);
+ ERR_FAIL_COND(!tile_data);
+
+ bool valid;
+ Variant value = tile_data->get(p_property, &valid);
+ if (!valid) {
+ return;
+ }
+ ERR_FAIL_COND(value.get_type() != Variant::FLOAT);
+
+ Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font("bold", "EditorFonts");
+ int height = font->get_height();
+ int width = 200;
+ p_canvas_item->draw_string(font, p_transform.get_origin() + Vector2i(-width / 2, height / 2), vformat("%.2f", value), HALIGN_CENTER, width, -1, Color(1, 1, 1), 1, Color(0, 0, 0, 1));
+}
+
+void TileDataPositionEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) {
+ TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile);
+ ERR_FAIL_COND(!tile_data);
+
+ bool valid;
+ Variant value = tile_data->get(p_property, &valid);
+ if (!valid) {
+ return;
+ }
+ ERR_FAIL_COND(value.get_type() != Variant::VECTOR2I && value.get_type() != Variant::VECTOR2);
+
+ Ref<Texture2D> position_icon = TileSetEditor::get_singleton()->get_theme_icon("EditorPosition", "EditorIcons");
+ p_canvas_item->draw_texture(position_icon, p_transform.get_origin() + Vector2(value) - position_icon->get_size() / 2);
+}
+
+void TileDataOcclusionShapeEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) {
+ TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile);
+ ERR_FAIL_COND(!tile_data);
+
+ Vector<String> components = String(p_property).split("/", true);
+ if (components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_integer()) {
+ int occlusion_layer = components[0].trim_prefix("occlusion_layer_").to_int();
+ if (occlusion_layer >= 0 && occlusion_layer < p_tile_set->get_occlusion_layers_count()) {
+ // Draw all shapes.
+ Vector<Color> debug_occlusion_color;
+ debug_occlusion_color.push_back(Color(0.5, 0, 0, 0.6));
+
+ RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform);
+ Ref<OccluderPolygon2D> occluder = tile_data->get_occluder(occlusion_layer);
+ if (occluder.is_valid() && occluder->get_polygon().size() >= 3) {
+ p_canvas_item->draw_polygon(Variant(occluder->get_polygon()), debug_occlusion_color);
+ }
+ RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D());
+ }
+ }
+}
+
+void TileDataCollisionShapeEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) {
+ TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile);
+ ERR_FAIL_COND(!tile_data);
+
+ Vector<String> components = String(p_property).split("/", true);
+ if (components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_integer()) {
+ int physics_layer = components[0].trim_prefix("physics_layer_").to_int();
+ if (physics_layer >= 0 && physics_layer < p_tile_set->get_physics_layers_count()) {
+ // Draw all shapes.
+ Color debug_collision_color = p_canvas_item->get_tree()->get_debug_collisions_color();
+ RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform);
+ for (int i = 0; i < tile_data->get_collision_shapes_count(physics_layer); i++) {
+ Ref<Shape2D> shape = tile_data->get_collision_shape_shape(physics_layer, i);
+ if (shape.is_valid()) {
+ shape->draw(p_canvas_item->get_canvas_item(), debug_collision_color);
+ }
+ }
+ RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D());
+ }
+ }
+}
+
+void TileDataTerrainsEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) {
+ TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile);
+ ERR_FAIL_COND(!tile_data);
+
+ 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);
+ }
+}
+
+void TileDataNavigationPolygonEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) {
+ TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile);
+ ERR_FAIL_COND(!tile_data);
+
+ Vector<String> components = String(p_property).split("/", true);
+ if (components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_integer()) {
+ int navigation_layer = components[0].trim_prefix("navigation_layer_").to_int();
+ if (navigation_layer >= 0 && navigation_layer < p_tile_set->get_navigation_layers_count()) {
+ // Draw all shapes.
+ RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform);
+
+ Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(navigation_layer);
+ if (navigation_polygon.is_valid()) {
+ Vector<Vector2> verts = navigation_polygon->get_vertices();
+ if (verts.size() < 3) {
+ return;
+ }
+
+ Color color = p_canvas_item->get_tree()->get_debug_navigation_color();
+
+ RandomPCG rand;
+ for (int i = 0; i < navigation_polygon->get_polygon_count(); i++) {
+ // An array of vertices for this polygon.
+ Vector<int> polygon = navigation_polygon->get_polygon(i);
+ Vector<Vector2> vertices;
+ vertices.resize(polygon.size());
+ for (int j = 0; j < polygon.size(); j++) {
+ ERR_FAIL_INDEX(polygon[j], verts.size());
+ vertices.write[j] = verts[polygon[j]];
+ }
+
+ // Generate the polygon color, slightly randomly modified from the settings one.
+ Color random_variation_color;
+ random_variation_color.set_hsv(color.get_h() + rand.random(-1.0, 1.0) * 0.05, color.get_s(), color.get_v() + rand.random(-1.0, 1.0) * 0.1);
+ random_variation_color.a = color.a;
+ Vector<Color> colors;
+ colors.push_back(random_variation_color);
+
+ RenderingServer::get_singleton()->canvas_item_add_polygon(p_canvas_item->get_canvas_item(), vertices, colors);
+ }
+ }
+
+ RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D());
+ }
+ }
+}
diff --git a/editor/plugins/tiles/tile_data_editors.h b/editor/plugins/tiles/tile_data_editors.h
new file mode 100644
index 0000000000..f4f9c25157
--- /dev/null
+++ b/editor/plugins/tiles/tile_data_editors.h
@@ -0,0 +1,110 @@
+/*************************************************************************/
+/* tile_data_editors.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_DATA_EDITORS_H
+#define TILE_DATA_EDITORS_H
+
+#include "scene/gui/control.h"
+#include "scene/resources/tile_set.h"
+
+class TileDataEditor : public Control {
+ GDCLASS(TileDataEditor, Control);
+
+protected:
+ TileData *tile_data;
+ String property;
+
+ TileData *_get_tile_data(TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile);
+
+public:
+ // Edits a TileData property.
+ void edit(TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property);
+
+ // Used to draw the value over a tile.
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property){};
+};
+
+class TileDataTextureOffsetEditor : public TileDataEditor {
+ GDCLASS(TileDataTextureOffsetEditor, TileDataEditor);
+
+public:
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override;
+};
+
+class TileDataIntegerEditor : public TileDataEditor {
+ GDCLASS(TileDataIntegerEditor, TileDataEditor);
+
+public:
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override;
+};
+
+class TileDataFloatEditor : public TileDataEditor {
+ GDCLASS(TileDataFloatEditor, TileDataEditor);
+
+public:
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override;
+};
+
+class TileDataPositionEditor : public TileDataEditor {
+ GDCLASS(TileDataPositionEditor, TileDataEditor);
+
+public:
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override;
+};
+
+class TileDataOcclusionShapeEditor : public TileDataEditor {
+ GDCLASS(TileDataOcclusionShapeEditor, TileDataEditor);
+
+public:
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override;
+};
+
+class TileDataCollisionShapeEditor : public TileDataEditor {
+ GDCLASS(TileDataCollisionShapeEditor, TileDataEditor);
+
+public:
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override;
+};
+
+class TileDataTerrainsEditor : public TileDataEditor {
+ GDCLASS(TileDataTerrainsEditor, TileDataEditor);
+
+public:
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override;
+};
+
+class TileDataNavigationPolygonEditor : public TileDataEditor {
+ GDCLASS(TileDataNavigationPolygonEditor, TileDataEditor);
+
+public:
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override;
+};
+
+#endif // TILE_DATA_EDITORS_H
diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp
new file mode 100644
index 0000000000..5937472e2b
--- /dev/null
+++ b/editor/plugins/tiles/tile_map_editor.cpp
@@ -0,0 +1,3368 @@
+/*************************************************************************/
+/* tile_map_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_map_editor.h"
+
+#include "tiles_editor_plugin.h"
+
+#include "editor/editor_scale.h"
+#include "editor/plugins/canvas_item_editor_plugin.h"
+
+#include "scene/gui/center_container.h"
+#include "scene/gui/split_container.h"
+
+#include "core/input/input.h"
+#include "core/math/geometry_2d.h"
+#include "core/os/keyboard.h"
+
+void TileMapEditorTilesPlugin::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED:
+ select_tool_button->set_icon(get_theme_icon("ToolSelect", "EditorIcons"));
+ paint_tool_button->set_icon(get_theme_icon("Edit", "EditorIcons"));
+ line_tool_button->set_icon(get_theme_icon("CurveLinear", "EditorIcons"));
+ rect_tool_button->set_icon(get_theme_icon("Rectangle", "EditorIcons"));
+ bucket_tool_button->set_icon(get_theme_icon("Bucket", "EditorIcons"));
+
+ 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");
+ break;
+ case NOTIFICATION_VISIBILITY_CHANGED:
+ _stop_dragging();
+ }
+}
+
+void TileMapEditorTilesPlugin::tile_set_changed() {
+ _update_fix_selected_and_hovered();
+ _update_bottom_panel();
+ _update_tile_set_sources_list();
+ _update_atlas_view();
+}
+
+void TileMapEditorTilesPlugin::_on_random_tile_checkbox_toggled(bool p_pressed) {
+ scatter_spinbox->set_editable(p_pressed);
+}
+
+void TileMapEditorTilesPlugin::_on_scattering_spinbox_changed(double p_value) {
+ scattering = p_value;
+}
+
+void TileMapEditorTilesPlugin::_update_toolbar() {
+ // Stop draggig if needed.
+ _stop_dragging();
+
+ // Hide all settings.
+ for (int i = 0; i < tools_settings->get_child_count(); i++) {
+ Object::cast_to<CanvasItem>(tools_settings->get_child(i))->hide();
+ }
+
+ // Show only the correct settings.
+ if (tool_buttons_group->get_pressed_button() == select_tool_button) {
+ } else if (tool_buttons_group->get_pressed_button() == paint_tool_button) {
+ tools_settings_vsep->show();
+ picker_button->show();
+ erase_button->show();
+ tools_settings_vsep_2->show();
+ random_tile_checkbox->show();
+ scatter_label->show();
+ scatter_spinbox->show();
+ } else if (tool_buttons_group->get_pressed_button() == line_tool_button) {
+ tools_settings_vsep->show();
+ picker_button->show();
+ erase_button->show();
+ tools_settings_vsep_2->show();
+ random_tile_checkbox->show();
+ scatter_label->show();
+ scatter_spinbox->show();
+ } else if (tool_buttons_group->get_pressed_button() == rect_tool_button) {
+ tools_settings_vsep->show();
+ picker_button->show();
+ erase_button->show();
+ tools_settings_vsep_2->show();
+ random_tile_checkbox->show();
+ scatter_label->show();
+ scatter_spinbox->show();
+ } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button) {
+ tools_settings_vsep->show();
+ picker_button->show();
+ erase_button->show();
+ tools_settings_vsep_2->show();
+ bucket_continuous_checkbox->show();
+ random_tile_checkbox->show();
+ scatter_label->show();
+ scatter_spinbox->show();
+ }
+}
+
+Control *TileMapEditorTilesPlugin::get_toolbar() const {
+ return toolbar;
+}
+
+void TileMapEditorTilesPlugin::_update_tile_set_sources_list() {
+ // Update the sources.
+ int old_current = sources_list->get_current();
+ sources_list->clear();
+
+ 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;
+ }
+
+ 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);
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ Ref<Texture2D> 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);
+ } else {
+ sources_list->add_item(vformat("No Texture Atlas Source - (id:%d)", source_id), missing_texture_texture);
+ }
+ } else {
+ sources_list->add_item(vformat("Unknown Type Source - (id:%d)", source_id), missing_texture_texture);
+ }
+ sources_list->set_item_metadata(i, source_id);
+ }
+
+ if (sources_list->get_item_count() > 0) {
+ if (old_current > 0) {
+ // Keep the current selected item if needed.
+ sources_list->set_current(CLAMP(old_current, 0, sources_list->get_item_count() - 1));
+ } else {
+ sources_list->set_current(0);
+ }
+ sources_list->emit_signal("item_selected", sources_list->get_current());
+ }
+
+ // Synchronize
+ TilesEditor::get_singleton()->set_atlas_sources_lists_current(sources_list->get_current());
+}
+
+void TileMapEditorTilesPlugin::_update_atlas_view() {
+ // Update the atlas display.
+ 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()) {
+ tile_atlas_view->hide();
+ return;
+ }
+
+ int source_index = sources_list->get_current();
+ if (source_index >= 0 && source_index < sources_list->get_item_count()) {
+ 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);
+ if (atlas_source) {
+ tile_atlas_view->set_atlas_source(*tile_map->get_tileset(), atlas_source, source_id);
+ tile_atlas_view->show();
+ }
+ } else {
+ tile_atlas_view->hide();
+ }
+
+ // Synchronize atlas view.
+ TilesEditor::get_singleton()->synchronize_atlas_view(tile_atlas_view);
+
+ tile_atlas_control->update();
+}
+
+void TileMapEditorTilesPlugin::_update_bottom_panel() {
+ 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();
+
+ // 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);
+}
+
+bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
+ if (!is_visible_in_tree()) {
+ // If the bottom editor is not visible, we ignore inputs.
+ return false;
+ }
+
+ if (CanvasItemEditor::get_singleton()->get_current_tool() != CanvasItemEditor::TOOL_SELECT) {
+ return false;
+ }
+
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return false;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return false;
+ }
+
+ // Shortcuts
+ if (ED_IS_SHORTCUT("tiles_editor/cut", p_event) || ED_IS_SHORTCUT("tiles_editor/copy", p_event)) {
+ // Fill in the clipboard.
+ if (!tile_map_selection.is_empty()) {
+ memdelete(tile_map_clipboard);
+ TypedArray<Vector2i> coords_array;
+ for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
+ coords_array.push_back(E->get());
+ }
+ tile_map_clipboard = tile_map->get_pattern(coords_array);
+ }
+
+ if (ED_IS_SHORTCUT("tiles_editor/cut", p_event)) {
+ // Delete selected tiles.
+ 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_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());
+ tile_map_selection.clear();
+ undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection());
+ undo_redo->commit_action();
+ }
+ }
+
+ return true;
+ }
+ if (ED_IS_SHORTCUT("tiles_editor/paste", p_event)) {
+ if (drag_type == DRAG_TYPE_NONE) {
+ drag_type = DRAG_TYPE_CLIPBOARD_PASTE;
+ }
+ CanvasItemEditor::get_singleton()->update_viewport();
+ return true;
+ }
+ if (ED_IS_SHORTCUT("tiles_editor/cancel", p_event)) {
+ if (drag_type == DRAG_TYPE_CLIPBOARD_PASTE) {
+ drag_type = DRAG_TYPE_NONE;
+ CanvasItemEditor::get_singleton()->update_viewport();
+ return true;
+ }
+ }
+ if (ED_IS_SHORTCUT("tiles_editor/delete", p_event)) {
+ // Delete selected tiles.
+ 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_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());
+ tile_map_selection.clear();
+ undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection());
+ undo_redo->commit_action();
+ }
+ return true;
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ has_mouse = true;
+ Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform();
+ Vector2 mpos = xform.affine_inverse().xform(mm->get_position());
+
+ switch (drag_type) {
+ case DRAG_TYPE_PAINT: {
+ Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_last_mouse_pos, mpos);
+ for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) {
+ if (!erase_button->is_pressed() && E->get().source_id == -1) {
+ continue;
+ }
+ Vector2i coords = E->key();
+ if (!drag_modified.has(coords)) {
+ 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, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ } break;
+ case DRAG_TYPE_BUCKET: {
+ Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos));
+ for (int i = 0; i < line.size(); i++) {
+ if (!drag_modified.has(line[i])) {
+ Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_continuous_checkbox->is_pressed());
+ for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) {
+ if (!erase_button->is_pressed() && E->get().source_id == -1) {
+ continue;
+ }
+ Vector2i coords = E->key();
+ if (!drag_modified.has(coords)) {
+ 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, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ }
+ }
+ } break;
+ default:
+ break;
+ }
+ drag_last_mouse_pos = mpos;
+ CanvasItemEditor::get_singleton()->update_viewport();
+
+ return true;
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
+ has_mouse = true;
+ Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform();
+ Vector2 mpos = xform.affine_inverse().xform(mb->get_position());
+
+ if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed()) {
+ // Pressed
+ if (tool_buttons_group->get_pressed_button() == select_tool_button) {
+ drag_start_mouse_pos = mpos;
+ if (tile_map_selection.has(tile_map->world_to_map(drag_start_mouse_pos)) && !mb->get_shift()) {
+ // Move the selection
+ drag_type = DRAG_TYPE_MOVE;
+ drag_modified.clear();
+ 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);
+ }
+ } else {
+ // Select tiles
+ drag_type = DRAG_TYPE_SELECT;
+ }
+ } else {
+ // Check if we are picking a tile.
+ if (picker_button->is_pressed()) {
+ drag_type = DRAG_TYPE_PICK;
+ drag_start_mouse_pos = mpos;
+ } else {
+ // Paint otherwise.
+ if (tool_buttons_group->get_pressed_button() == paint_tool_button) {
+ drag_type = DRAG_TYPE_PAINT;
+ drag_start_mouse_pos = mpos;
+ drag_modified.clear();
+ Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, mpos, mpos);
+ for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) {
+ if (!erase_button->is_pressed() && E->get().source_id == -1) {
+ continue;
+ }
+ Vector2i coords = E->key();
+ if (!drag_modified.has(coords)) {
+ 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, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ } else if (tool_buttons_group->get_pressed_button() == line_tool_button) {
+ drag_type = DRAG_TYPE_LINE;
+ drag_start_mouse_pos = mpos;
+ drag_modified.clear();
+ } else if (tool_buttons_group->get_pressed_button() == rect_tool_button) {
+ drag_type = DRAG_TYPE_RECT;
+ drag_start_mouse_pos = mpos;
+ drag_modified.clear();
+ } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button) {
+ drag_type = DRAG_TYPE_BUCKET;
+ drag_start_mouse_pos = mpos;
+ drag_modified.clear();
+ Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos));
+ for (int i = 0; i < line.size(); i++) {
+ if (!drag_modified.has(line[i])) {
+ Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_continuous_checkbox->is_pressed());
+ for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) {
+ if (!erase_button->is_pressed() && E->get().source_id == -1) {
+ continue;
+ }
+ Vector2i coords = E->key();
+ if (!drag_modified.has(coords)) {
+ 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, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ } else {
+ // Released
+ _stop_dragging();
+ }
+
+ CanvasItemEditor::get_singleton()->update_viewport();
+
+ return true;
+ }
+ drag_last_mouse_pos = mpos;
+ }
+
+ return false;
+}
+
+void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_overlay) {
+ 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;
+ }
+
+ if (!tile_map->is_visible_in_tree()) {
+ return;
+ }
+
+ Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform();
+ Vector2i tile_shape_size = tile_set->get_tile_size();
+
+ // Draw the selection.
+ if (is_visible_in_tree() && tool_buttons_group->get_pressed_button() == select_tool_button) {
+ // In select mode, we only draw the current selection if we are modifying it (pressing control or shift).
+ if (drag_type == DRAG_TYPE_MOVE || (drag_type == DRAG_TYPE_SELECT && !Input::get_singleton()->is_key_pressed(KEY_CONTROL) && !Input::get_singleton()->is_key_pressed(KEY_SHIFT))) {
+ // Do nothing
+ } else {
+ tile_map->draw_cells_outline(p_overlay, tile_map_selection, Color(0.0, 0.0, 1.0), xform);
+ }
+ }
+
+ // 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;
+
+ if (drag_type == DRAG_TYPE_PICK) {
+ // Draw the area being picvked.
+ Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos) - tile_map->world_to_map(drag_start_mouse_pos)).abs();
+ rect.size += Vector2i(1, 1);
+ for (int x = rect.position.x; x < rect.get_end().x; x++) {
+ for (int y = rect.position.y; y < rect.get_end().y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ if (tile_map->get_cell_source_id(coords) != -1) {
+ Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(coords) - tile_shape_size / 2, tile_shape_size));
+ tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0), false);
+ }
+ }
+ }
+ } else if (drag_type == DRAG_TYPE_SELECT) {
+ // Draw the area being selected.
+ Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos) - tile_map->world_to_map(drag_start_mouse_pos)).abs();
+ rect.size += Vector2i(1, 1);
+ Set<Vector2i> to_draw;
+ for (int x = rect.position.x; x < rect.get_end().x; x++) {
+ for (int y = rect.position.y; y < rect.get_end().y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ if (tile_map->get_cell_source_id(coords) != -1) {
+ to_draw.insert(coords);
+ }
+ }
+ }
+ tile_map->draw_cells_outline(p_overlay, to_draw, Color(1.0, 1.0, 1.0), xform);
+ } else if (drag_type == DRAG_TYPE_MOVE) {
+ // Preview when moving.
+ Vector2i top_left;
+ if (!tile_map_selection.is_empty()) {
+ top_left = tile_map_selection.front()->get();
+ }
+ for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
+ top_left = top_left.min(E->get());
+ }
+ Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left);
+ offset = tile_map->world_to_map(drag_last_mouse_pos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset);
+
+ TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells();
+ for (int i = 0; i < selection_used_cells.size(); i++) {
+ Vector2i coords = tile_map->map_pattern(offset + top_left, selection_used_cells[i], selection_pattern);
+ preview[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i]));
+ }
+ } else if (drag_type == DRAG_TYPE_CLIPBOARD_PASTE) {
+ // Preview when pasting.
+ Vector2 mouse_offset = (Vector2(tile_map_clipboard->get_size()) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size();
+ TypedArray<Vector2i> clipboard_used_cells = tile_map_clipboard->get_used_cells();
+ for (int i = 0; i < clipboard_used_cells.size(); i++) {
+ Vector2i coords = tile_map->map_pattern(tile_map->world_to_map(drag_last_mouse_pos - mouse_offset), clipboard_used_cells[i], tile_map_clipboard);
+ preview[coords] = TileMapCell(tile_map_clipboard->get_cell_source_id(clipboard_used_cells[i]), tile_map_clipboard->get_cell_atlas_coords(clipboard_used_cells[i]), tile_map_clipboard->get_cell_alternative_tile(clipboard_used_cells[i]));
+ }
+ } else if (!picker_button->is_pressed()) {
+ bool expand_grid = false;
+ if (tool_buttons_group->get_pressed_button() == paint_tool_button && drag_type == DRAG_TYPE_NONE) {
+ // Preview for a single pattern.
+ preview = _draw_line(drag_last_mouse_pos, drag_last_mouse_pos, drag_last_mouse_pos);
+ expand_grid = true;
+ } else if (tool_buttons_group->get_pressed_button() == line_tool_button) {
+ if (drag_type == DRAG_TYPE_NONE) {
+ // Preview for a single pattern.
+ preview = _draw_line(drag_last_mouse_pos, drag_last_mouse_pos, drag_last_mouse_pos);
+ expand_grid = true;
+ } else if (drag_type == DRAG_TYPE_LINE) {
+ // Preview for a line pattern.
+ preview = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, drag_last_mouse_pos);
+ expand_grid = true;
+ }
+ } else if (tool_buttons_group->get_pressed_button() == rect_tool_button && drag_type == DRAG_TYPE_RECT) {
+ // Preview for a line pattern.
+ preview = _draw_rect(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos));
+ expand_grid = true;
+ } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button && drag_type == DRAG_TYPE_NONE) {
+ // Preview for a line pattern.
+ preview = _draw_bucket_fill(tile_map->world_to_map(drag_last_mouse_pos), bucket_continuous_checkbox->is_pressed());
+ }
+
+ // Expand the grid if needed
+ if (expand_grid && !preview.is_empty()) {
+ drawn_grid_rect = Rect2i(preview.front()->key(), Vector2i(1, 1));
+ for (Map<Vector2i, TileMapCell>::Element *E = preview.front(); E; E = E->next()) {
+ drawn_grid_rect.expand_to(E->key());
+ }
+ }
+ }
+
+ if (!preview.is_empty()) {
+ const int fading = 5;
+
+ // Draw the lines of the grid behind the preview.
+ if (drawn_grid_rect.size.x > 0 && drawn_grid_rect.size.y > 0) {
+ drawn_grid_rect = drawn_grid_rect.grow(fading);
+ for (int x = drawn_grid_rect.position.x; x < (drawn_grid_rect.position.x + drawn_grid_rect.size.x); x++) {
+ for (int y = drawn_grid_rect.position.y; y < (drawn_grid_rect.position.y + drawn_grid_rect.size.y); y++) {
+ Vector2i pos_in_rect = Vector2i(x, y) - drawn_grid_rect.position;
+
+ // Fade out the border of the grid.
+ float left_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.x), 0.0f, 1.0f);
+ float right_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.x, (float)(drawn_grid_rect.size.x - fading), (float)pos_in_rect.x), 0.0f, 1.0f);
+ float top_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.y), 0.0f, 1.0f);
+ float bottom_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.y, (float)(drawn_grid_rect.size.y - fading), (float)pos_in_rect.y), 0.0f, 1.0f);
+ float opacity = CLAMP(MIN(left_opacity, MIN(right_opacity, MIN(top_opacity, bottom_opacity))) + 0.1, 0.0f, 1.0f);
+
+ Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(Vector2(x, y)) - tile_shape_size / 2, tile_shape_size));
+ tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 0.5, 0.2, 0.5 * opacity), false);
+ }
+ }
+ }
+
+ // Draw the preview.
+ for (Map<Vector2i, TileMapCell>::Element *E = preview.front(); E; E = E->next()) {
+ 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)) {
+ TileSetSource *source = *tile_set->get_source(E->get().source_id);
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ // Get tile data.
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile));
+
+ // Compute the offset
+ Rect2i source_rect = atlas_source->get_tile_texture_region(E->get().get_atlas_coords());
+ Vector2i tile_offset = atlas_source->get_tile_effective_texture_offset(E->get().get_atlas_coords(), E->get().alternative_tile);
+
+ // Compute the destination rectangle in the CanvasItem.
+ Rect2 dest_rect;
+ dest_rect.size = source_rect.size;
+
+ bool transpose = tile_data->get_transpose();
+ if (transpose) {
+ dest_rect.position = (tile_map->map_to_world(E->key()) - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset);
+ } else {
+ dest_rect.position = (tile_map->map_to_world(E->key()) - dest_rect.size / 2 - tile_offset);
+ }
+
+ dest_rect = xform.xform(dest_rect);
+
+ if (tile_data->get_flip_h()) {
+ dest_rect.size.x = -dest_rect.size.x;
+ }
+
+ if (tile_data->get_flip_v()) {
+ dest_rect.size.y = -dest_rect.size.y;
+ }
+
+ // Get the tile modulation.
+ Color modulate = tile_data->get_modulate();
+ Color self_modulate = tile_map->get_self_modulate();
+ modulate = Color(modulate.r * self_modulate.r, modulate.g * self_modulate.g, modulate.b * self_modulate.b, modulate.a * self_modulate.a);
+
+ // 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 {
+ 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);
+ }
+ }
+ }
+ }
+ }
+}
+
+void TileMapEditorTilesPlugin::_mouse_exited_viewport() {
+ has_mouse = false;
+ CanvasItemEditor::get_singleton()->update_viewport();
+}
+
+TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(const TileMapPattern *p_pattern) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return TileMapCell();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return TileMapCell();
+ }
+
+ TypedArray<Vector2i> used_cells = p_pattern->get_used_cells();
+ double sum = 0.0;
+ for (int i = 0; i < used_cells.size(); i++) {
+ int source_id = p_pattern->get_cell_source_id(used_cells[i]);
+ Vector2i atlas_coords = p_pattern->get_cell_atlas_coords(used_cells[i]);
+ int alternative_tile = p_pattern->get_cell_alternative_tile(used_cells[i]);
+
+ 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();
+ } else {
+ sum += 1.0;
+ }
+ }
+
+ double empty_probability = sum * scattering;
+ double current = 0.0;
+ double rand = Math::random(0.0, sum + empty_probability);
+ for (int i = 0; i < used_cells.size(); i++) {
+ int source_id = p_pattern->get_cell_source_id(used_cells[i]);
+ Vector2i atlas_coords = p_pattern->get_cell_atlas_coords(used_cells[i]);
+ int alternative_tile = p_pattern->get_cell_alternative_tile(used_cells[i]);
+
+ TileSetSource *source = *tile_set->get_source(source_id);
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ current += Object::cast_to<TileData>(atlas_source->get_tile_data(atlas_coords, alternative_tile))->get_probability();
+ } else {
+ current += 1.0;
+ }
+
+ if (current >= rand) {
+ return TileMapCell(source_id, atlas_coords, alternative_tile);
+ }
+ }
+ return TileMapCell();
+}
+
+Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2i p_to_mouse_pos) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return Map<Vector2i, TileMapCell>();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return Map<Vector2i, TileMapCell>();
+ }
+
+ // Get or create the pattern.
+ TileMapPattern erase_pattern;
+ erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
+ TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern;
+
+ Map<Vector2i, TileMapCell> output;
+ if (!pattern->is_empty()) {
+ // Paint the tiles on the tile map.
+ if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) {
+ // Paint a random tile.
+ Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(p_from_mouse_pos), tile_map->world_to_map(p_to_mouse_pos));
+ for (int i = 0; i < line.size(); i++) {
+ output.insert(line[i], _pick_random_tile(pattern));
+ }
+ } else {
+ // Paint the pattern.
+ // If we paint several tiles, we virtually move the mouse as if it was in the center of the "brush"
+ Vector2 mouse_offset = (Vector2(pattern->get_size()) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size();
+ Vector2i last_hovered_cell = tile_map->world_to_map(p_from_mouse_pos - mouse_offset);
+ Vector2i new_hovered_cell = tile_map->world_to_map(p_to_mouse_pos - mouse_offset);
+ Vector2i drag_start_cell = tile_map->world_to_map(p_start_drag_mouse_pos - mouse_offset);
+
+ TypedArray<Vector2i> used_cells = pattern->get_used_cells();
+ Vector2i offset = Vector2i(Math::posmod(drag_start_cell.x, pattern->get_size().x), Math::posmod(drag_start_cell.y, pattern->get_size().y)); // Note: no posmodv for Vector2i for now. Meh.s
+ Vector<Vector2i> line = TileMapEditor::get_line(tile_map, (last_hovered_cell - offset) / pattern->get_size(), (new_hovered_cell - offset) / pattern->get_size());
+ for (int i = 0; i < line.size(); i++) {
+ Vector2i top_left = line[i] * pattern->get_size() + offset;
+ for (int j = 0; j < used_cells.size(); j++) {
+ Vector2i coords = tile_map->map_pattern(top_left, used_cells[j], pattern);
+ output.insert(coords, TileMapCell(pattern->get_cell_source_id(used_cells[j]), pattern->get_cell_atlas_coords(used_cells[j]), pattern->get_cell_alternative_tile(used_cells[j])));
+ }
+ }
+ }
+ }
+ return output;
+}
+
+Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start_cell, Vector2i p_end_cell) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return Map<Vector2i, TileMapCell>();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return Map<Vector2i, TileMapCell>();
+ }
+
+ // Create the rect to draw.
+ Rect2i rect = Rect2i(p_start_cell, p_end_cell - p_start_cell).abs();
+ rect.size += Vector2i(1, 1);
+
+ // Get or create the pattern.
+ TileMapPattern erase_pattern;
+ erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
+ TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern;
+
+ // Compute the offset to align things to the bottom or right.
+ bool aligned_right = p_end_cell.x < p_start_cell.x;
+ bool valigned_bottom = p_end_cell.y < p_start_cell.y;
+ Vector2i offset = Vector2i(aligned_right ? -(pattern->get_size().x - (rect.get_size().x % pattern->get_size().x)) : 0, valigned_bottom ? -(pattern->get_size().y - (rect.get_size().y % pattern->get_size().y)) : 0);
+
+ Map<Vector2i, TileMapCell> output;
+ if (!pattern->is_empty()) {
+ if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) {
+ // Paint a random tile.
+ for (int x = 0; x < rect.size.x; x++) {
+ for (int y = 0; y < rect.size.y; y++) {
+ Vector2i coords = rect.position + Vector2i(x, y);
+ output.insert(coords, _pick_random_tile(pattern));
+ }
+ }
+ } else {
+ // Paint the pattern.
+ TypedArray<Vector2i> used_cells = pattern->get_used_cells();
+ for (int x = 0; x <= rect.size.x / pattern->get_size().x; x++) {
+ for (int y = 0; y <= rect.size.y / pattern->get_size().y; y++) {
+ Vector2i pattern_coords = rect.position + Vector2i(x, y) * pattern->get_size() + offset;
+ for (int j = 0; j < used_cells.size(); j++) {
+ Vector2i coords = pattern_coords + used_cells[j];
+ if (rect.has_point(coords)) {
+ output.insert(coords, TileMapCell(pattern->get_cell_source_id(used_cells[j]), pattern->get_cell_atlas_coords(used_cells[j]), pattern->get_cell_alternative_tile(used_cells[j])));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return output;
+}
+
+Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i p_coords, bool p_contiguous) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return Map<Vector2i, TileMapCell>();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return Map<Vector2i, TileMapCell>();
+ }
+
+ // Get or create the pattern.
+ TileMapPattern erase_pattern;
+ erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
+ TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern;
+
+ Map<Vector2i, TileMapCell> output;
+ if (!pattern->is_empty()) {
+ TileMapCell source = tile_map->get_cell(p_coords);
+
+ // If we are filling empty tiles, compute the tilemap boundaries.
+ Rect2i boundaries;
+ if (source.source_id == -1) {
+ boundaries = tile_map->get_used_rect();
+ }
+
+ if (p_contiguous) {
+ // Replace continuous tiles like the source.
+ Set<Vector2i> already_checked;
+ List<Vector2i> to_check;
+ to_check.push_back(p_coords);
+ while (!to_check.is_empty()) {
+ Vector2i coords = to_check.back()->get();
+ to_check.pop_back();
+ if (!already_checked.has(coords)) {
+ if (source.source_id == tile_map->get_cell_source_id(coords) &&
+ source.get_atlas_coords() == tile_map->get_cell_atlas_coords(coords) &&
+ source.alternative_tile == tile_map->get_cell_alternative_tile(coords) &&
+ (source.source_id != -1 || boundaries.has_point(coords))) {
+ if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) {
+ // Paint a random tile.
+ output.insert(coords, _pick_random_tile(pattern));
+ } else {
+ // Paint the pattern.
+ Vector2i pattern_coords = (coords - p_coords) % pattern->get_size(); // Note: it would be good to have posmodv for Vector2i.
+ pattern_coords.x = pattern_coords.x < 0 ? pattern_coords.x + pattern->get_size().x : pattern_coords.x;
+ pattern_coords.y = pattern_coords.y < 0 ? pattern_coords.y + pattern->get_size().y : pattern_coords.y;
+ if (pattern->has_cell(pattern_coords)) {
+ output.insert(coords, TileMapCell(pattern->get_cell_source_id(pattern_coords), pattern->get_cell_atlas_coords(pattern_coords), pattern->get_cell_alternative_tile(pattern_coords)));
+ } else {
+ output.insert(coords, TileMapCell());
+ }
+ }
+
+ // Get surrounding tiles (handles different tile shapes).
+ TypedArray<Vector2i> around = tile_map->get_surrounding_tiles(coords);
+ for (int i = 0; i < around.size(); i++) {
+ to_check.push_back(around[i]);
+ }
+ }
+ already_checked.insert(coords);
+ }
+ }
+ } else {
+ // Replace all tiles like the source.
+ TypedArray<Vector2i> to_check;
+ if (source.source_id == -1) {
+ Rect2i rect = tile_map->get_used_rect();
+ if (rect.size.x <= 0 || rect.size.y <= 0) {
+ rect = Rect2i(p_coords, Vector2i(1, 1));
+ }
+ for (int x = boundaries.position.x; x < boundaries.get_end().x; x++) {
+ for (int y = boundaries.position.y; y < boundaries.get_end().y; y++) {
+ to_check.append(Vector2i(x, y));
+ }
+ }
+ } else {
+ to_check = tile_map->get_used_cells();
+ }
+ for (int i = 0; i < to_check.size(); i++) {
+ Vector2i coords = to_check[i];
+ if (source.source_id == tile_map->get_cell_source_id(coords) &&
+ source.get_atlas_coords() == tile_map->get_cell_atlas_coords(coords) &&
+ source.alternative_tile == tile_map->get_cell_alternative_tile(coords) &&
+ (source.source_id != -1 || boundaries.has_point(coords))) {
+ if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) {
+ // Paint a random tile.
+ output.insert(coords, _pick_random_tile(pattern));
+ } else {
+ // Paint the pattern.
+ Vector2i pattern_coords = (coords - p_coords) % pattern->get_size(); // Note: it would be good to have posmodv for Vector2i.
+ pattern_coords.x = pattern_coords.x < 0 ? pattern_coords.x + pattern->get_size().x : pattern_coords.x;
+ pattern_coords.y = pattern_coords.y < 0 ? pattern_coords.y + pattern->get_size().y : pattern_coords.y;
+ if (pattern->has_cell(pattern_coords)) {
+ output.insert(coords, TileMapCell(pattern->get_cell_source_id(pattern_coords), pattern->get_cell_atlas_coords(pattern_coords), pattern->get_cell_alternative_tile(pattern_coords)));
+ } else {
+ output.insert(coords, TileMapCell());
+ }
+ }
+ }
+ }
+ }
+ }
+ return output;
+}
+
+void TileMapEditorTilesPlugin::_stop_dragging() {
+ if (drag_type == DRAG_TYPE_NONE) {
+ return;
+ }
+
+ 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;
+ }
+
+ Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform();
+ Vector2 mpos = xform.affine_inverse().xform(CanvasItemEditor::get_singleton()->get_viewport_control()->get_local_mouse_position());
+
+ switch (drag_type) {
+ case DRAG_TYPE_SELECT: {
+ undo_redo->create_action(TTR("Change selection"));
+ undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection());
+
+ if (!Input::get_singleton()->is_key_pressed(KEY_SHIFT) && !Input::get_singleton()->is_key_pressed(KEY_CONTROL)) {
+ tile_map_selection.clear();
+ }
+ Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos) - tile_map->world_to_map(drag_start_mouse_pos)).abs();
+ for (int x = rect.position.x; x <= rect.get_end().x; x++) {
+ for (int y = rect.position.y; y <= rect.get_end().y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ if (Input::get_singleton()->is_key_pressed(KEY_CONTROL)) {
+ if (tile_map_selection.has(coords)) {
+ tile_map_selection.erase(coords);
+ }
+ } else {
+ if (tile_map->get_cell_source_id(coords) != -1) {
+ tile_map_selection.insert(coords);
+ }
+ }
+ }
+ }
+ undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection());
+ undo_redo->commit_action(false);
+
+ _update_selection_pattern_from_tilemap_selection();
+ _update_tileset_selection_from_selection_pattern();
+ } break;
+ case DRAG_TYPE_MOVE: {
+ Vector2i top_left;
+ if (!tile_map_selection.is_empty()) {
+ top_left = tile_map_selection.front()->get();
+ }
+ for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
+ top_left = top_left.min(E->get());
+ }
+
+ Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left);
+ offset = tile_map->world_to_map(mpos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset);
+
+ TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells();
+
+ Vector2i coords;
+ Map<Vector2i, TileMapCell> cells_undo;
+ for (int i = 0; i < selection_used_cells.size(); i++) {
+ coords = tile_map->map_pattern(top_left, selection_used_cells[i], selection_pattern);
+ cells_undo[coords] = TileMapCell(drag_modified[coords].source_id, drag_modified[coords].get_atlas_coords(), drag_modified[coords].alternative_tile);
+ coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
+ cells_undo[coords] = TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords));
+ }
+
+ Map<Vector2i, TileMapCell> cells_do;
+ for (int i = 0; i < selection_used_cells.size(); i++) {
+ coords = tile_map->map_pattern(top_left, selection_used_cells[i], selection_pattern);
+ cells_do[coords] = TileMapCell();
+ }
+ for (int i = 0; i < selection_used_cells.size(); i++) {
+ coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
+ cells_do[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i]));
+ }
+ undo_redo->create_action(TTR("Move tiles"));
+ // Move the tiles.
+ for (Map<Vector2i, TileMapCell>::Element *E = cells_do.front(); E; E = E->next()) {
+ undo_redo->add_do_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ for (Map<Vector2i, TileMapCell>::Element *E = cells_undo.front(); E; E = E->next()) {
+ undo_redo->add_undo_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+
+ // Update the selection.
+ undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection());
+ tile_map_selection.clear();
+ for (int i = 0; i < selection_used_cells.size(); i++) {
+ coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
+ tile_map_selection.insert(coords);
+ }
+ undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection());
+ undo_redo->commit_action();
+ } break;
+ case DRAG_TYPE_PICK: {
+ Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos) - tile_map->world_to_map(drag_start_mouse_pos)).abs();
+ rect.size += Vector2i(1, 1);
+ memdelete(selection_pattern);
+ TypedArray<Vector2i> coords_array;
+ for (int x = rect.position.x; x < rect.get_end().x; x++) {
+ for (int y = rect.position.y; y < rect.get_end().y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ if (tile_map->get_cell_source_id(coords) != -1) {
+ coords_array.push_back(coords);
+ }
+ }
+ }
+ selection_pattern = tile_map->get_pattern(coords_array);
+ if (!selection_pattern->is_empty()) {
+ _update_tileset_selection_from_selection_pattern();
+ } else {
+ _update_selection_pattern_from_tileset_selection();
+ }
+ picker_button->set_pressed(false);
+ } break;
+ case DRAG_TYPE_PAINT: {
+ undo_redo->create_action(TTR("Paint tiles"));
+ for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) {
+ undo_redo->add_do_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key()));
+ undo_redo->add_undo_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ undo_redo->commit_action(false);
+ } break;
+ case DRAG_TYPE_LINE: {
+ Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, mpos);
+ undo_redo->create_action(TTR("Paint tiles"));
+ for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) {
+ if (!erase_button->is_pressed() && E->get().source_id == -1) {
+ continue;
+ }
+ undo_redo->add_do_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ undo_redo->add_undo_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key()));
+ }
+ undo_redo->commit_action();
+ } break;
+ case DRAG_TYPE_RECT: {
+ Map<Vector2i, TileMapCell> to_draw = _draw_rect(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos));
+ undo_redo->create_action(TTR("Paint tiles"));
+ for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) {
+ if (!erase_button->is_pressed() && E->get().source_id == -1) {
+ continue;
+ }
+ undo_redo->add_do_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ undo_redo->add_undo_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key()));
+ }
+ undo_redo->commit_action();
+ } break;
+ case DRAG_TYPE_BUCKET: {
+ undo_redo->create_action(TTR("Paint tiles"));
+ for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) {
+ if (!erase_button->is_pressed() && E->get().source_id == -1) {
+ continue;
+ }
+ undo_redo->add_do_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key()));
+ undo_redo->add_undo_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ undo_redo->commit_action(false);
+ } break;
+ case DRAG_TYPE_CLIPBOARD_PASTE: {
+ Vector2 mouse_offset = (Vector2(tile_map_clipboard->get_size()) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size();
+ undo_redo->create_action(TTR("Paste tiles"));
+ TypedArray<Vector2i> used_cells = tile_map_clipboard->get_used_cells();
+ for (int i = 0; i < used_cells.size(); i++) {
+ Vector2i coords = tile_map->map_pattern(tile_map->world_to_map(mpos - mouse_offset), used_cells[i], tile_map_clipboard);
+ undo_redo->add_do_method(tile_map, "set_cell", coords, tile_map_clipboard->get_cell_source_id(used_cells[i]), tile_map_clipboard->get_cell_atlas_coords(used_cells[i]), tile_map_clipboard->get_cell_alternative_tile(used_cells[i]));
+ undo_redo->add_undo_method(tile_map, "set_cell", coords, tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords));
+ }
+ undo_redo->commit_action();
+ } break;
+ default:
+ break;
+ }
+ drag_type = DRAG_TYPE_NONE;
+}
+
+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;
+ tile_set_selection.clear();
+ tile_map_selection.clear();
+ selection_pattern->clear();
+ return;
+ }
+
+ 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;
+ tile_set_selection.clear();
+ tile_map_selection.clear();
+ selection_pattern->clear();
+ return;
+ }
+
+ 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;
+ tile_set_selection.clear();
+ tile_map_selection.clear();
+ selection_pattern->clear();
+ return;
+ }
+
+ int source_id = sources_list->get_item_metadata(source_index);
+
+ // Clear hovered if needed.
+ if (source_id != hovered_tile.source_id ||
+ !tile_set->has_source(hovered_tile.source_id) ||
+ !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;
+ }
+
+ // Selection if needed.
+ for (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) {
+ const TileMapCell *selected = &(E->get());
+ if (!tile_set->has_source(selected->source_id) ||
+ !tile_set->get_source(selected->source_id)->has_tile(selected->get_atlas_coords()) ||
+ !tile_set->get_source(selected->source_id)->has_alternative_tile(selected->get_atlas_coords(), selected->alternative_tile)) {
+ tile_set_selection.erase(E);
+ }
+ }
+
+ if (!tile_map_selection.is_empty()) {
+ _update_selection_pattern_from_tilemap_selection();
+ } else {
+ _update_selection_pattern_from_tileset_selection();
+ }
+}
+
+void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection() {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ memdelete(selection_pattern);
+
+ TypedArray<Vector2i> coords_array;
+ for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
+ coords_array.push_back(E->get());
+ }
+ selection_pattern = tile_map->get_pattern(coords_array);
+}
+
+void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection() {
+ 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;
+ }
+
+ // Clear the tilemap selection.
+ tile_map_selection.clear();
+
+ // Clear the selected pattern.
+ selection_pattern->clear();
+
+ // Group per source.
+ Map<int, List<const TileMapCell *>> per_source;
+ for (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) {
+ per_source[E->get().source_id].push_back(&(E->get()));
+ }
+
+ 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;
+ Rect2i encompassing_rect_coords;
+ Map<Vector2i, const TileMapCell *> organized_pattern;
+
+ TileSetSource *source = *tile_set->get_source(E_source->key());
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ // 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 {
+ organized_pattern[current->get_atlas_coords()] = 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());
+ }
+ } else {
+ // Add everything unorganized.
+ for (List<const TileMapCell *>::Element *E_cell = E_source->get().front(); E_cell; E_cell = E_cell->next()) {
+ unorganized.push_back(E_cell->get());
+ }
+ }
+
+ // 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);
+ }
+ Vector2i organized_size = selection_pattern->get_size();
+ 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);
+ }
+ }
+ CanvasItemEditor::get_singleton()->update_viewport();
+}
+
+void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern() {
+ tile_set_selection.clear();
+ TypedArray<Vector2i> used_cells = selection_pattern->get_used_cells();
+ for (int i = 0; i < used_cells.size(); i++) {
+ Vector2i coords = used_cells[i];
+ if (selection_pattern->get_cell_source_id(coords) != -1) {
+ 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();
+}
+
+void TileMapEditorTilesPlugin::_tile_atlas_control_draw() {
+ 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_index = sources_list->get_current();
+ if (source_index < 0 || source_index >= sources_list->get_item_count()) {
+ return;
+ }
+
+ int source_id = sources_list->get_item_metadata(source_index);
+ if (!tile_set->has_source(source_id)) {
+ return;
+ }
+
+ TileSetAtlasSource *atlas = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id));
+ if (!atlas) {
+ return;
+ }
+
+ // 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().alternative_tile == 0) {
+ tile_atlas_control->draw_rect(atlas->get_tile_texture_region(E->get().get_atlas_coords()), Color(0.0, 0.0, 1.0), false);
+ }
+ }
+
+ // Draw the hovered tile.
+ if (hovered_tile.get_atlas_coords() != TileSetAtlasSource::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);
+ }
+
+ // Draw the selection rect.
+ if (tile_set_dragging_selection) {
+ 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());
+
+ Rect2i region = Rect2i(start_tile, end_tile - start_tile).abs();
+ region.size += Vector2i(1, 1);
+
+ Set<Vector2i> to_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) {
+ to_draw.insert(tile);
+ }
+ }
+ }
+
+ for (Set<Vector2i>::Element *E = to_draw.front(); E; E = E->next()) {
+ tile_atlas_control->draw_rect(atlas->get_tile_texture_region(E->get()), Color(0.8, 0.8, 1.0), false);
+ }
+ }
+}
+
+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;
+ tile_set_dragging_selection = false;
+ tile_atlas_control->update();
+}
+
+void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEvent> &p_event) {
+ 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_index = sources_list->get_current();
+ if (source_index < 0 || source_index >= sources_list->get_item_count()) {
+ return;
+ }
+
+ int source_id = sources_list->get_item_metadata(source_index);
+ if (!tile_set->has_source(source_id)) {
+ return;
+ }
+
+ TileSetAtlasSource *atlas = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id));
+ if (!atlas) {
+ return;
+ }
+
+ // 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;
+ Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
+ if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+ coords = atlas->get_tile_at_coords(coords);
+ if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+ hovered_tile.set_atlas_coords(coords);
+ hovered_tile.alternative_tile = 0;
+ }
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ tile_atlas_control->update();
+ alternative_tiles_control->update();
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed()) { // Pressed
+ tile_set_dragging_selection = true;
+ tile_set_drag_start_mouse_pos = tile_atlas_control->get_local_mouse_position();
+ if (!mb->get_shift()) {
+ tile_set_selection.clear();
+ }
+
+ if (hovered_tile.get_atlas_coords() != TileSetAtlasSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0) {
+ if (mb->get_shift() && 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 {
+ tile_set_selection.insert(TileMapCell(source_id, hovered_tile.get_atlas_coords(), 0));
+ }
+ }
+ _update_selection_pattern_from_tileset_selection();
+ } else { // Released
+ if (tile_set_dragging_selection) {
+ if (!mb->get_shift()) {
+ tile_set_selection.clear();
+ }
+ // 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) {
+ 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->get_shift() && start_coords != TileSetAtlasSource::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))) {
+ tile_set_selection.erase(TileMapCell(source_id, tile_coords, 0));
+ }
+ }
+ }
+ } else {
+ // Insert in 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.insert(TileMapCell(source_id, tile_coords, 0));
+ }
+ }
+ }
+ }
+ }
+ _update_selection_pattern_from_tileset_selection();
+ }
+ tile_set_dragging_selection = false;
+ }
+ tile_atlas_control->update();
+ }
+}
+
+void TileMapEditorTilesPlugin::_tile_alternatives_control_draw() {
+ 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_index = sources_list->get_current();
+ if (source_index < 0 || source_index >= sources_list->get_item_count()) {
+ return;
+ }
+
+ int source_id = sources_list->get_item_metadata(source_index);
+ if (!tile_set->has_source(source_id)) {
+ return;
+ }
+
+ TileSetAtlasSource *atlas = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id));
+ if (!atlas) {
+ return;
+ }
+
+ // 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) {
+ 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);
+ }
+ }
+ }
+
+ // Draw hovered tile.
+ if (hovered_tile.get_atlas_coords() != TileSetAtlasSource::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);
+ }
+ }
+}
+
+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;
+ tile_set_dragging_selection = false;
+ alternative_tiles_control->update();
+}
+
+void TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event) {
+ 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_index = sources_list->get_current();
+ if (source_index < 0 || source_index >= sources_list->get_item_count()) {
+ return;
+ }
+
+ int source_id = sources_list->get_item_metadata(source_index);
+ if (!tile_set->has_source(source_id)) {
+ return;
+ }
+
+ TileSetAtlasSource *atlas = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id));
+ if (!atlas) {
+ return;
+ }
+
+ // 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;
+ 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) {
+ hovered_tile.set_atlas_coords(coords);
+ hovered_tile.alternative_tile = alternative;
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ tile_atlas_control->update();
+ alternative_tiles_control->update();
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed()) { // Pressed
+ // Left click pressed.
+ if (!mb->get_shift()) {
+ tile_set_selection.clear();
+ }
+
+ if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && alternative != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) {
+ if (mb->get_shift() && 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 {
+ tile_set_selection.insert(TileMapCell(source_id, hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile));
+ }
+ }
+ _update_selection_pattern_from_tileset_selection();
+ }
+ tile_atlas_control->update();
+ alternative_tiles_control->update();
+ }
+}
+
+void TileMapEditorTilesPlugin::_set_tile_map_selection(const TypedArray<Vector2i> &p_selection) {
+ tile_map_selection.clear();
+ for (int i = 0; i < p_selection.size(); i++) {
+ tile_map_selection.insert(p_selection[i]);
+ }
+ _update_selection_pattern_from_tilemap_selection();
+ _update_tileset_selection_from_selection_pattern();
+ CanvasItemEditor::get_singleton()->update_viewport();
+}
+
+TypedArray<Vector2i> TileMapEditorTilesPlugin::_get_tile_map_selection() const {
+ TypedArray<Vector2i> output;
+ for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
+ output.push_back(E->get());
+ }
+ return output;
+}
+
+void TileMapEditorTilesPlugin::edit(ObjectID p_tile_map_id) {
+ tile_map_id = p_tile_map_id;
+
+ // Clean the selection.
+ tile_set_selection.clear();
+ tile_map_selection.clear();
+ selection_pattern->clear();
+}
+
+void TileMapEditorTilesPlugin::_bind_methods() {
+ 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);
+}
+
+TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
+ CanvasItemEditor::get_singleton()->get_viewport_control()->connect("mouse_exited", callable_mp(this, &TileMapEditorTilesPlugin::_mouse_exited_viewport));
+
+ // --- Shortcuts ---
+ ED_SHORTCUT("tiles_editor/cut", TTR("Cut"), KEY_MASK_CMD | KEY_X);
+ ED_SHORTCUT("tiles_editor/copy", TTR("Copy"), KEY_MASK_CMD | KEY_C);
+ ED_SHORTCUT("tiles_editor/paste", TTR("Paste"), KEY_MASK_CMD | KEY_V);
+ ED_SHORTCUT("tiles_editor/cancel", TTR("Cancel"), KEY_ESCAPE);
+ ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), KEY_DELETE);
+
+ // --- Toolbar ---
+ toolbar = memnew(HBoxContainer);
+
+ HBoxContainer *tilemap_tiles_tools_buttons = memnew(HBoxContainer);
+
+ tool_buttons_group.instance();
+
+ select_tool_button = memnew(Button);
+ select_tool_button->set_flat(true);
+ select_tool_button->set_toggle_mode(true);
+ select_tool_button->set_button_group(tool_buttons_group);
+ select_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/selection_tool", "Selection", KEY_S));
+ select_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
+ tilemap_tiles_tools_buttons->add_child(select_tool_button);
+
+ paint_tool_button = memnew(Button);
+ paint_tool_button->set_flat(true);
+ paint_tool_button->set_toggle_mode(true);
+ paint_tool_button->set_button_group(tool_buttons_group);
+ paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", "Paint", KEY_E));
+ paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
+ tilemap_tiles_tools_buttons->add_child(paint_tool_button);
+
+ line_tool_button = memnew(Button);
+ line_tool_button->set_flat(true);
+ line_tool_button->set_toggle_mode(true);
+ line_tool_button->set_button_group(tool_buttons_group);
+ line_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/line_tool", "Line", KEY_L));
+ line_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
+ tilemap_tiles_tools_buttons->add_child(line_tool_button);
+
+ rect_tool_button = memnew(Button);
+ rect_tool_button->set_flat(true);
+ rect_tool_button->set_toggle_mode(true);
+ rect_tool_button->set_button_group(tool_buttons_group);
+ rect_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/rect_tool", "Rect", KEY_R));
+ rect_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
+ tilemap_tiles_tools_buttons->add_child(rect_tool_button);
+
+ bucket_tool_button = memnew(Button);
+ bucket_tool_button->set_flat(true);
+ bucket_tool_button->set_toggle_mode(true);
+ bucket_tool_button->set_button_group(tool_buttons_group);
+ bucket_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/bucket_tool", "Bucket", KEY_B));
+ bucket_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
+ tilemap_tiles_tools_buttons->add_child(bucket_tool_button);
+
+ toolbar->add_child(tilemap_tiles_tools_buttons);
+
+ // -- TileMap tool settings --
+ tools_settings = memnew(HBoxContainer);
+ toolbar->add_child(tools_settings);
+
+ tools_settings_vsep = memnew(VSeparator);
+ tools_settings->add_child(tools_settings_vsep);
+
+ // Picker
+ picker_button = memnew(Button);
+ picker_button->set_flat(true);
+ picker_button->set_toggle_mode(true);
+ picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P));
+ picker_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport));
+ tools_settings->add_child(picker_button);
+
+ // Erase button.
+ erase_button = memnew(Button);
+ erase_button->set_flat(true);
+ erase_button->set_toggle_mode(true);
+ erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", KEY_E));
+ erase_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport));
+ tools_settings->add_child(erase_button);
+
+ // Separator 2.
+ tools_settings_vsep_2 = memnew(VSeparator);
+ tools_settings->add_child(tools_settings_vsep_2);
+
+ // Continuous checkbox.
+ bucket_continuous_checkbox = memnew(CheckBox);
+ bucket_continuous_checkbox->set_flat(true);
+ bucket_continuous_checkbox->set_text(TTR("Contiguous"));
+ tools_settings->add_child(bucket_continuous_checkbox);
+
+ // Random tile checkbox.
+ random_tile_checkbox = memnew(CheckBox);
+ random_tile_checkbox->set_flat(true);
+ random_tile_checkbox->set_text(TTR("Place Random Tile"));
+ random_tile_checkbox->connect("toggled", callable_mp(this, &TileMapEditorTilesPlugin::_on_random_tile_checkbox_toggled));
+ tools_settings->add_child(random_tile_checkbox);
+
+ // Random tile scattering.
+ scatter_label = memnew(Label);
+ scatter_label->set_tooltip(TTR("Defines the probability of painting nothing instead of a randomly selected tile."));
+ scatter_label->set_text(TTR("Scattering:"));
+ tools_settings->add_child(scatter_label);
+
+ scatter_spinbox = memnew(SpinBox);
+ scatter_spinbox->set_min(0.0);
+ scatter_spinbox->set_max(1000);
+ scatter_spinbox->set_step(0.001);
+ scatter_spinbox->set_tooltip(TTR("Defines the probability of painting nothing instead of a randomly selected tile."));
+ scatter_spinbox->get_line_edit()->add_theme_constant_override("minimum_character_width", 4);
+ scatter_spinbox->connect("value_changed", callable_mp(this, &TileMapEditorTilesPlugin::_on_scattering_spinbox_changed));
+ tools_settings->add_child(scatter_spinbox);
+
+ _on_random_tile_checkbox_toggled(false);
+
+ // Default tool.
+ paint_tool_button->set_pressed(true);
+ _update_toolbar();
+
+ // --- Bottom panel ---
+ set_name("Tiles");
+
+ missing_source_label = memnew(Label);
+ missing_source_label->set_text(TTR("This TileMap's TileSet has no source configured. Edit the TileSet resource to add one."));
+ missing_source_label->set_h_size_flags(SIZE_EXPAND_FILL);
+ missing_source_label->set_v_size_flags(SIZE_EXPAND_FILL);
+ missing_source_label->set_align(Label::ALIGN_CENTER);
+ missing_source_label->set_valign(Label::VALIGN_CENTER);
+ missing_source_label->hide();
+ add_child(missing_source_label);
+
+ atlas_sources_split_container = memnew(HSplitContainer);
+ atlas_sources_split_container->set_h_size_flags(SIZE_EXPAND_FILL);
+ atlas_sources_split_container->set_v_size_flags(SIZE_EXPAND_FILL);
+ add_child(atlas_sources_split_container);
+
+ sources_list = memnew(ItemList);
+ sources_list->set_fixed_icon_size(Size2i(60, 60) * EDSCALE);
+ 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->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(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_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);
+ tile_atlas_control->connect("draw", callable_mp(this, &TileMapEditorTilesPlugin::_tile_atlas_control_draw));
+ tile_atlas_control->connect("mouse_exited", callable_mp(this, &TileMapEditorTilesPlugin::_tile_atlas_control_mouse_exited));
+ tile_atlas_control->connect("gui_input", callable_mp(this, &TileMapEditorTilesPlugin::_tile_atlas_control_gui_input));
+ tile_atlas_view->add_control_over_atlas_tiles(tile_atlas_control);
+
+ alternative_tiles_control = memnew(Control);
+ alternative_tiles_control->connect("draw", callable_mp(this, &TileMapEditorTilesPlugin::_tile_alternatives_control_draw));
+ alternative_tiles_control->connect("mouse_exited", callable_mp(this, &TileMapEditorTilesPlugin::_tile_alternatives_control_mouse_exited));
+ 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);
+
+ _update_bottom_panel();
+}
+
+TileMapEditorTilesPlugin::~TileMapEditorTilesPlugin() {
+ memdelete(selection_pattern);
+ memdelete(tile_map_clipboard);
+}
+
+void TileMapEditorTerrainsPlugin::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED:
+ paint_tool_button->set_icon(get_theme_icon("Edit", "EditorIcons"));
+ picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons"));
+ erase_button->set_icon(get_theme_icon("Eraser", "EditorIcons"));
+ break;
+ }
+}
+
+void TileMapEditorTerrainsPlugin::tile_set_changed() {
+ _update_terrains_cache();
+ _update_terrains_tree();
+ _update_tiles_list();
+}
+
+void TileMapEditorTerrainsPlugin::_update_toolbar() {
+ // Hide all settings.
+ for (int i = 0; i < tools_settings->get_child_count(); i++) {
+ Object::cast_to<CanvasItem>(tools_settings->get_child(i))->hide();
+ }
+
+ // Show only the correct settings.
+ if (tool_buttons_group->get_pressed_button() == paint_tool_button) {
+ tools_settings_vsep->show();
+ picker_button->show();
+ erase_button->show();
+ }
+}
+
+Control *TileMapEditorTerrainsPlugin::get_toolbar() const {
+ return toolbar;
+}
+
+Map<Vector2i, TileSet::CellNeighbor> TileMapEditorTerrainsPlugin::Constraint::get_overlapping_coords_and_peering_bits() const {
+ Map<Vector2i, TileSet::CellNeighbor> output;
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ ERR_FAIL_COND_V(!tile_set.is_valid(), output);
+
+ TileSet::TileShape shape = tile_set->get_tile_shape();
+ if (shape == TileSet::TILE_SHAPE_SQUARE) {
+ switch (bit) {
+ case 0:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE;
+ break;
+ case 1:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
+ break;
+ case 2:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE;
+ break;
+ case 3:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
+ break;
+ default:
+ ERR_FAIL_V(output);
+ }
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ switch (bit) {
+ case 0:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER;
+ break;
+ case 1:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE;
+ break;
+ case 2:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER;
+ break;
+ case 3:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
+ break;
+ default:
+ ERR_FAIL_V(output);
+ }
+ } else {
+ // Half offset shapes.
+ TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis();
+ if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ switch (bit) {
+ case 0:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE;
+ break;
+ case 1:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER;
+ break;
+ case 2:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE;
+ break;
+ case 3:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
+ break;
+ case 4:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
+ break;
+ default:
+ ERR_FAIL_V(output);
+ }
+ } else {
+ switch (bit) {
+ case 0:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ break;
+ case 1:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE;
+ break;
+ case 2:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ break;
+ case 3:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE;
+ break;
+ case 4:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
+ break;
+ default:
+ ERR_FAIL_V(output);
+ }
+ }
+ }
+ return output;
+}
+
+TileMapEditorTerrainsPlugin::Constraint::Constraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain) {
+ // The way we build the constraint make it easy to detect conflicting constraints.
+ tile_map = p_tile_map;
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ TileSet::TileShape shape = tile_set->get_tile_shape();
+ if (shape == TileSet::TILE_SHAPE_SQUARE || shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_SIDE:
+ case TileSet::CELL_NEIGHBOR_RIGHT_CORNER:
+ bit = 0;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ bit = 1;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE:
+ case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER:
+ bit = 2;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ bit = 3;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_SIDE:
+ case TileSet::CELL_NEIGHBOR_LEFT_CORNER:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_SIDE:
+ case TileSet::CELL_NEIGHBOR_TOP_CORNER:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ bit = 3;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit);
+ break;
+ default:
+ ERR_FAIL();
+ break;
+ }
+ } else {
+ // Half-offset shapes
+ TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis();
+ if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_SIDE:
+ bit = 0;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ bit = 1;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ bit = 2;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER:
+ bit = 3;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ bit = 4;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_SIDE:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ bit = 3;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ bit = 4;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ bit = 3;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ break;
+ default:
+ ERR_FAIL();
+ break;
+ }
+ } else {
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_CORNER:
+ bit = 0;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ bit = 1;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ bit = 2;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE:
+ bit = 3;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ bit = 4;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_CORNER:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_SIDE:
+ bit = 3;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ bit = 4;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ break;
+ default:
+ ERR_FAIL();
+ break;
+ }
+ }
+ }
+ terrain = p_terrain;
+}
+
+Set<TileMapEditorTerrainsPlugin::TerrainsTilePattern> TileMapEditorTerrainsPlugin::_get_valid_terrains_tile_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<Constraint> p_constraints) const {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return Set<TerrainsTilePattern>();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return Set<TerrainsTilePattern>();
+ }
+
+ // Returns all tiles compatible with the given constraints.
+ Set<TerrainsTilePattern> compatible_terrain_tile_patterns;
+ for (Map<TerrainsTilePattern, Set<TileMapCell>>::Element *E = per_terrain_terrains_tile_patterns_tiles[p_terrain_set].front(); E; E = E->next()) {
+ int valid = true;
+ int in_pattern_count = 0;
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, bit)) {
+ // Check if the bit is compatible with the constraints.
+ Constraint terrain_bit_constraint = Constraint(tile_map, p_position, bit, E->key()[in_pattern_count]);
+
+ Set<Constraint>::Element *in_set_constraint_element = p_constraints.find(terrain_bit_constraint);
+ if (in_set_constraint_element && in_set_constraint_element->get().get_terrain() != terrain_bit_constraint.get_terrain()) {
+ valid = false;
+ break;
+ }
+ in_pattern_count++;
+ }
+ }
+
+ if (valid) {
+ compatible_terrain_tile_patterns.insert(E->key());
+ }
+ }
+
+ return compatible_terrain_tile_patterns;
+}
+
+Set<TileMapEditorTerrainsPlugin::Constraint> TileMapEditorTerrainsPlugin::_get_constraints_from_removed_cells_list(const Set<Vector2i> &p_to_replace, int p_terrain_set) const {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return Set<Constraint>();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return Set<Constraint>();
+ }
+
+ ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), Set<Constraint>());
+
+ // Build a set of dummy constraints get the constrained points.
+ Set<Constraint> dummy_constraints;
+ for (Set<Vector2i>::Element *E = p_to_replace.front(); E; E = E->next()) {
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { // Iterates over sides.
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, bit)) {
+ dummy_constraints.insert(Constraint(tile_map, E->get(), bit, -1));
+ }
+ }
+ }
+
+ // For each constrained point, we get all overlapping tiles, and select the most adequate terrain for it.
+ Set<Constraint> constraints;
+ for (Set<Constraint>::Element *E = dummy_constraints.front(); E; E = E->next()) {
+ Constraint c = E->get();
+
+ Map<int, int> terrain_count;
+
+ // Count the number of occurences per terrain.
+ Map<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits();
+ for (Map<Vector2i, TileSet::CellNeighbor>::Element *E_overlapping = overlapping_terrain_bits.front(); E_overlapping; E_overlapping = E_overlapping->next()) {
+ if (!p_to_replace.has(E_overlapping->key())) {
+ TileMapCell neighbor_cell = tile_map->get_cell(E_overlapping->key());
+ TileData *neighbor_tile_data = nullptr;
+ if (terrain_tiles.has(neighbor_cell) && terrain_tiles[neighbor_cell]->get_terrain_set() == p_terrain_set) {
+ neighbor_tile_data = terrain_tiles[neighbor_cell];
+ }
+
+ int terrain = neighbor_tile_data ? neighbor_tile_data->get_peering_bit_terrain(TileSet::CellNeighbor(E_overlapping->get())) : -1;
+ if (terrain_count.has(terrain)) {
+ terrain_count[terrain] = 0;
+ }
+ terrain_count[terrain] += 1;
+ }
+ }
+
+ // Get the terrain with the max number of occurences.
+ int max = 0;
+ int max_terrain = -1;
+ for (Map<int, int>::Element *E_terrain_count = terrain_count.front(); E_terrain_count; E_terrain_count = E_terrain_count->next()) {
+ if (E_terrain_count->get() > max) {
+ max = E_terrain_count->get();
+ max_terrain = E_terrain_count->key();
+ }
+ }
+
+ // Set the adequate terrain.
+ if (max > 0) {
+ c.set_terrain(max_terrain);
+ constraints.insert(c);
+ }
+ }
+
+ return constraints;
+}
+
+Set<TileMapEditorTerrainsPlugin::Constraint> TileMapEditorTerrainsPlugin::_get_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TerrainsTilePattern p_terrains_tile_pattern) const {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return Set<TileMapEditorTerrainsPlugin::Constraint>();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return Set<TileMapEditorTerrainsPlugin::Constraint>();
+ }
+
+ // Compute the constraints needed from the surrounding tiles.
+ Set<TileMapEditorTerrainsPlugin::Constraint> output;
+ int in_pattern_count = 0;
+ for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor side = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, side)) {
+ Constraint c = Constraint(tile_map, p_position, side, p_terrains_tile_pattern[in_pattern_count]);
+ output.insert(c);
+ in_pattern_count++;
+ }
+ }
+
+ return output;
+}
+
+Map<Vector2i, TileMapEditorTerrainsPlugin::TerrainsTilePattern> TileMapEditorTerrainsPlugin::_wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TileMapEditorTerrainsPlugin::Constraint> p_constraints) const {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return Map<Vector2i, TerrainsTilePattern>();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return Map<Vector2i, TileMapEditorTerrainsPlugin::TerrainsTilePattern>();
+ }
+
+ // Copy the constraints set.
+ Set<TileMapEditorTerrainsPlugin::Constraint> constraints = p_constraints;
+
+ // Compute all acceptable tiles for each cell.
+ Map<Vector2i, Set<TerrainsTilePattern>> per_cell_acceptable_tiles;
+ for (Set<Vector2i>::Element *E = p_to_replace.front(); E; E = E->next()) {
+ per_cell_acceptable_tiles[E->get()] = _get_valid_terrains_tile_patterns_for_constraints(p_terrain_set, E->get(), constraints);
+ }
+
+ // Ouput map.
+ Map<Vector2i, TerrainsTilePattern> output;
+
+ // Add all positions to a set.
+ Set<Vector2i> to_replace = Set<Vector2i>(p_to_replace);
+ while (!to_replace.is_empty()) {
+ // Compute the minimum number of tile possibilities for each cell.
+ int min_nb_possibilities = 100000000;
+ for (Map<Vector2i, Set<TerrainsTilePattern>>::Element *E = per_cell_acceptable_tiles.front(); E; E = E->next()) {
+ min_nb_possibilities = MIN(min_nb_possibilities, E->get().size());
+ }
+
+ // Get the set of possible cells to fill.
+ LocalVector<Vector2i> to_choose_from;
+ for (Map<Vector2i, Set<TerrainsTilePattern>>::Element *E = per_cell_acceptable_tiles.front(); E; E = E->next()) {
+ if (E->get().size() == min_nb_possibilities) {
+ to_choose_from.push_back(E->key());
+ }
+ }
+
+ // Randomly pick a tile out of the most constrained.
+ Vector2i selected_cell_to_replace = to_choose_from[Math::random(0, to_choose_from.size() - 1)];
+
+ // Randomly select a tile out of them the put it in the grid.
+ Set<TerrainsTilePattern> valid_tiles = per_cell_acceptable_tiles[selected_cell_to_replace];
+ if (valid_tiles.is_empty()) {
+ // No possibilities :/
+ break;
+ }
+ int random_terrain_tile_pattern_index = Math::random(0, valid_tiles.size() - 1);
+ Set<TerrainsTilePattern>::Element *E = valid_tiles.front();
+ for (int i = 0; i < random_terrain_tile_pattern_index; i++) {
+ E = E->next();
+ }
+ TerrainsTilePattern selected_terrain_tile_pattern = E->get();
+
+ // Set the selected cell into the output.
+ output[selected_cell_to_replace] = selected_terrain_tile_pattern;
+ to_replace.erase(selected_cell_to_replace);
+ per_cell_acceptable_tiles.erase(selected_cell_to_replace);
+
+ // Add the new constraints from the added tiles.
+ Set<TileMapEditorTerrainsPlugin::Constraint> new_constraints = _get_constraints_from_added_tile(selected_cell_to_replace, p_terrain_set, selected_terrain_tile_pattern);
+ for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E_constraint = new_constraints.front(); E_constraint; E_constraint = E_constraint->next()) {
+ constraints.insert(E_constraint->get());
+ }
+
+ // Compute valid tiles again for neighbors.
+ for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor side = TileSet::CellNeighbor(i);
+ if (tile_map->is_existing_neighbor(side)) {
+ Vector2i neighbor = tile_map->get_neighbor_cell(selected_cell_to_replace, side);
+ if (to_replace.has(neighbor)) {
+ per_cell_acceptable_tiles[neighbor] = _get_valid_terrains_tile_patterns_for_constraints(p_terrain_set, neighbor, constraints);
+ }
+ }
+ }
+ }
+ return output;
+}
+
+TileMapCell TileMapEditorTerrainsPlugin::_get_random_tile_from_pattern(int p_terrain_set, TerrainsTilePattern p_terrain_tile_pattern) const {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return TileMapCell();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return TileMapCell();
+ }
+
+ // Count the sum of probabilities.
+ double sum = 0.0;
+ Set<TileMapCell> set = per_terrain_terrains_tile_patterns_tiles[p_terrain_set][p_terrain_tile_pattern];
+ for (Set<TileMapCell>::Element *E = set.front(); E; E = E->next()) {
+ if (E->get().source_id >= 0) {
+ Ref<TileSetSource> source = tile_set->get_source(E->get().source_id);
+
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile));
+ sum += tile_data->get_probability();
+ } else {
+ sum += 1.0;
+ }
+ } else {
+ sum += 1.0;
+ }
+ }
+
+ // Generate a random number.
+ double count = 0.0;
+ double picked = Math::random(0.0, sum);
+
+ // Pick the tile.
+ for (Set<TileMapCell>::Element *E = set.front(); E; E = E->next()) {
+ if (E->get().source_id >= 0) {
+ Ref<TileSetSource> source = tile_set->get_source(E->get().source_id);
+
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile));
+ count += tile_data->get_probability();
+ } else {
+ count += 1.0;
+ }
+ } else {
+ count += 1.0;
+ }
+
+ if (count >= picked) {
+ return E->get();
+ }
+ }
+
+ ERR_FAIL_V(TileMapCell());
+}
+
+Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map<Vector2i, TerrainsTilePattern> &p_to_paint, int p_terrain_set) const {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return Map<Vector2i, TileMapCell>();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return Map<Vector2i, TileMapCell>();
+ }
+
+ Map<Vector2i, TileMapCell> output;
+
+ // Add the constraints from the added tiles.
+ Set<TileMapEditorTerrainsPlugin::Constraint> added_tiles_constraints_set;
+ for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) {
+ Vector2i coords = E_to_paint->key();
+ TerrainsTilePattern terrains_tile_pattern = E_to_paint->get();
+
+ Set<TileMapEditorTerrainsPlugin::Constraint> cell_constraints = _get_constraints_from_added_tile(coords, p_terrain_set, terrains_tile_pattern);
+ for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E = cell_constraints.front(); E; E = E->next()) {
+ added_tiles_constraints_set.insert(E->get());
+ }
+ }
+
+ // Build the list of potential tiles to replace.
+ Set<Vector2i> potential_to_replace;
+ for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) {
+ Vector2i coords = E_to_paint->key();
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ if (tile_map->is_existing_neighbor(TileSet::CellNeighbor(i))) {
+ Vector2i neighbor = tile_map->get_neighbor_cell(coords, TileSet::CellNeighbor(i));
+ if (!p_to_paint.has(neighbor)) {
+ potential_to_replace.insert(neighbor);
+ }
+ }
+ }
+ }
+
+ // Set of tiles to replace
+ Set<Vector2i> to_replace;
+
+ // Add the central tiles to the one to replace. TODO: maybe change that.
+ for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) {
+ to_replace.insert(E_to_paint->key());
+ }
+
+ // Add the constraints from the surroundings of the modified areas.
+ Set<TileMapEditorTerrainsPlugin::Constraint> removed_cells_constraints_set;
+ bool to_replace_modified = true;
+ while (to_replace_modified) {
+ // Get the constraints from the removed cells.
+ removed_cells_constraints_set = _get_constraints_from_removed_cells_list(to_replace, p_terrain_set);
+
+ // Filter the sources to make sure they are in the potential_to_replace.
+ Map<Constraint, Set<Vector2i>> source_tiles_of_constraint;
+ for (Set<Constraint>::Element *E = removed_cells_constraints_set.front(); E; E = E->next()) {
+ Map<Vector2i, TileSet::CellNeighbor> sources_of_constraint = E->get().get_overlapping_coords_and_peering_bits();
+ for (Map<Vector2i, TileSet::CellNeighbor>::Element *E_source_tile_of_constraint = sources_of_constraint.front(); E_source_tile_of_constraint; E_source_tile_of_constraint = E_source_tile_of_constraint->next()) {
+ if (potential_to_replace.has(E_source_tile_of_constraint->key())) {
+ source_tiles_of_constraint[E->get()].insert(E_source_tile_of_constraint->key());
+ }
+ }
+ }
+
+ to_replace_modified = false;
+ for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E = added_tiles_constraints_set.front(); E; E = E->next()) {
+ Constraint c = E->get();
+ // Check if we have a conflict in constraints.
+ if (removed_cells_constraints_set.has(c) && removed_cells_constraints_set.find(c)->get().get_terrain() != c.get_terrain()) {
+ // If we do, we search for a neighbor to remove.
+ if (source_tiles_of_constraint.has(c) && !source_tiles_of_constraint[c].is_empty()) {
+ // Remove it.
+ Vector2i to_add_to_remove = source_tiles_of_constraint[c].front()->get();
+ potential_to_replace.erase(to_add_to_remove);
+ to_replace.insert(to_add_to_remove);
+ to_replace_modified = true;
+ for (Map<Constraint, Set<Vector2i>>::Element *E_source_tiles_of_constraint = source_tiles_of_constraint.front(); E_source_tiles_of_constraint; E_source_tiles_of_constraint = E_source_tiles_of_constraint->next()) {
+ E_source_tiles_of_constraint->get().erase(to_add_to_remove);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // Combine all constraints together.
+ Set<TileMapEditorTerrainsPlugin::Constraint> constraints = removed_cells_constraints_set;
+ for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E = added_tiles_constraints_set.front(); E; E = E->next()) {
+ constraints.insert(E->get());
+ }
+
+ // Run WFC to fill the holes with the constraints.
+ Map<Vector2i, TerrainsTilePattern> wfc_output = _wave_function_collapse(to_replace, p_terrain_set, constraints);
+
+ // Use the WFC run for the output.
+ for (Map<Vector2i, TerrainsTilePattern>::Element *E = wfc_output.front(); E; E = E->next()) {
+ output[E->key()] = _get_random_tile_from_pattern(p_terrain_set, E->get());
+ }
+
+ // Override the WFC results to make sure at least the painted tiles are acutally painted.
+ for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) {
+ output[E_to_paint->key()] = _get_random_tile_from_pattern(p_terrain_set, E_to_paint->get());
+ }
+
+ return output;
+}
+
+bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
+ if (!is_visible_in_tree()) {
+ // If the bottom editor is not visible, we ignore inputs.
+ return false;
+ }
+
+ if (CanvasItemEditor::get_singleton()->get_current_tool() != CanvasItemEditor::TOOL_SELECT) {
+ return false;
+ }
+
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return false;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return false;
+ }
+
+ // Get the selected terrain.
+ TerrainsTilePattern selected_terrains_tile_pattern;
+ int selected_terrain_set = -1;
+
+ TreeItem *selected_tree_item = terrains_tree->get_selected();
+ if (selected_tree_item && selected_tree_item->get_metadata(0)) {
+ Dictionary metadata_dict = selected_tree_item->get_metadata(0);
+ // Selected terrain
+ selected_terrain_set = metadata_dict["terrain_set"];
+
+ // Selected tile
+ if (erase_button->is_pressed()) {
+ selected_terrains_tile_pattern.clear();
+ for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor side = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(selected_terrain_set, side)) {
+ selected_terrains_tile_pattern.push_back(-1);
+ }
+ }
+ } else if (terrains_tile_list->is_anything_selected()) {
+ metadata_dict = terrains_tile_list->get_item_metadata(terrains_tile_list->get_selected_items()[0]);
+ selected_terrains_tile_pattern = metadata_dict["terrains_tile_pattern"];
+ }
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform();
+ Vector2 mpos = xform.affine_inverse().xform(mm->get_position());
+
+ switch (drag_type) {
+ case DRAG_TYPE_PAINT: {
+ if (selected_terrain_set >= 0) {
+ Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos));
+ Map<Vector2i, TerrainsTilePattern> to_draw;
+ for (int i = 0; i < line.size(); i++) {
+ to_draw[line[i]] = selected_terrains_tile_pattern;
+ }
+ Map<Vector2i, TileMapCell> modified = _draw_terrains(to_draw, selected_terrain_set);
+ for (Map<Vector2i, TileMapCell>::Element *E = modified.front(); E; E = E->next()) {
+ if (!drag_modified.has(E->key())) {
+ drag_modified[E->key()] = tile_map->get_cell(E->key());
+ }
+ tile_map->set_cell(E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ }
+ } break;
+ default:
+ break;
+ }
+ drag_last_mouse_pos = mpos;
+ CanvasItemEditor::get_singleton()->update_viewport();
+
+ return true;
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
+ Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform();
+ Vector2 mpos = xform.affine_inverse().xform(mb->get_position());
+
+ if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed()) {
+ // Pressed
+ if (picker_button->is_pressed()) {
+ drag_type = DRAG_TYPE_PICK;
+ } else {
+ // Paint otherwise.
+ if (selected_terrain_set >= 0 && !selected_terrains_tile_pattern.is_empty() && tool_buttons_group->get_pressed_button() == paint_tool_button) {
+ drag_type = DRAG_TYPE_PAINT;
+ drag_start_mouse_pos = mpos;
+
+ drag_modified.clear();
+
+ Map<Vector2i, TerrainsTilePattern> terrains_to_draw;
+ terrains_to_draw[tile_map->world_to_map(mpos)] = selected_terrains_tile_pattern;
+
+ Map<Vector2i, TileMapCell> to_draw = _draw_terrains(terrains_to_draw, selected_terrain_set);
+ for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) {
+ drag_modified[E->key()] = tile_map->get_cell(E->key());
+ tile_map->set_cell(E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ }
+ }
+ } else {
+ // Released
+ switch (drag_type) {
+ case DRAG_TYPE_PICK: {
+ Vector2i coords = tile_map->world_to_map(mpos);
+ TileMapCell tile = tile_map->get_cell(coords);
+
+ if (terrain_tiles.has(tile)) {
+ Array terrains_tile_pattern = _build_terrains_tile_pattern(terrain_tiles[tile]);
+
+ // Find the tree item for the right terrain set.
+ bool need_tree_item_switch = true;
+ TreeItem *tree_item = terrains_tree->get_selected();
+ if (tree_item) {
+ Dictionary metadata_dict = tree_item->get_metadata(0);
+ if (metadata_dict.has("terrain_set") && metadata_dict.has("terrain_id")) {
+ int terrain_set = metadata_dict["terrain_set"];
+ int terrain_id = metadata_dict["terrain_id"];
+ if (per_terrain_terrains_tile_patterns[terrain_set][terrain_id].has(terrains_tile_pattern)) {
+ need_tree_item_switch = false;
+ }
+ }
+ }
+
+ if (need_tree_item_switch) {
+ for (tree_item = terrains_tree->get_root()->get_children(); tree_item; tree_item = tree_item->get_next_visible()) {
+ Dictionary metadata_dict = tree_item->get_metadata(0);
+ if (metadata_dict.has("terrain_set") && metadata_dict.has("terrain_id")) {
+ int terrain_set = metadata_dict["terrain_set"];
+ int terrain_id = metadata_dict["terrain_id"];
+ if (per_terrain_terrains_tile_patterns[terrain_set][terrain_id].has(terrains_tile_pattern)) {
+ // Found
+ tree_item->select(0);
+ _update_tiles_list();
+ break;
+ }
+ }
+ }
+ }
+
+ // Find the list item for the given tile.
+ if (tree_item) {
+ for (int i = 0; i < terrains_tile_list->get_item_count(); i++) {
+ Dictionary metadata_dict = terrains_tile_list->get_item_metadata(i);
+ TerrainsTilePattern in_meta_terrains_tile_pattern = metadata_dict["terrains_tile_pattern"];
+ bool equals = true;
+ for (int j = 0; j < terrains_tile_pattern.size(); j++) {
+ if (terrains_tile_pattern[j] != in_meta_terrains_tile_pattern[j]) {
+ equals = false;
+ break;
+ }
+ }
+ if (equals) {
+ terrains_tile_list->select(i);
+ break;
+ }
+ }
+ } else {
+ ERR_PRINT("Terrain tile not found.");
+ }
+ }
+ picker_button->set_pressed(false);
+ } break;
+ case DRAG_TYPE_PAINT: {
+ undo_redo->create_action(TTR("Paint terrain"));
+ for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) {
+ undo_redo->add_do_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key()));
+ undo_redo->add_undo_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ undo_redo->commit_action(false);
+ } break;
+ default:
+ break;
+ }
+ drag_type = DRAG_TYPE_NONE;
+ }
+
+ CanvasItemEditor::get_singleton()->update_viewport();
+
+ return true;
+ }
+ drag_last_mouse_pos = mpos;
+ }
+
+ return false;
+}
+
+TileMapEditorTerrainsPlugin::TerrainsTilePattern TileMapEditorTerrainsPlugin::_build_terrains_tile_pattern(TileData *p_tile_data) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return TerrainsTilePattern();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return TerrainsTilePattern();
+ }
+
+ TerrainsTilePattern output;
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ if (tile_set->is_valid_peering_bit_terrain(p_tile_data->get_terrain_set(), TileSet::CellNeighbor(i))) {
+ output.push_back(p_tile_data->get_peering_bit_terrain(TileSet::CellNeighbor(i)));
+ }
+ }
+ return output;
+}
+
+void TileMapEditorTerrainsPlugin::_update_terrains_cache() {
+ 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;
+ }
+
+ // Compute the tile sides.
+ tile_sides.clear();
+ TileSet::TileShape shape = tile_set->get_tile_shape();
+ if (shape == TileSet::TILE_SHAPE_SQUARE) {
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_RIGHT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ } else {
+ if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_RIGHT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ } else {
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ }
+ }
+
+ // Organizes tiles into structures.
+ per_terrain_terrains_tile_patterns_tiles.resize(tile_set->get_terrain_sets_count());
+ per_terrain_terrains_tile_patterns.resize(tile_set->get_terrain_sets_count());
+ for (int i = 0; i < tile_set->get_terrain_sets_count(); i++) {
+ per_terrain_terrains_tile_patterns_tiles[i].clear();
+ per_terrain_terrains_tile_patterns[i].resize(tile_set->get_terrains_count(i));
+ for (int j = 0; j < (int)per_terrain_terrains_tile_patterns[i].size(); j++) {
+ per_terrain_terrains_tile_patterns[i][j].clear();
+ }
+ }
+
+ for (int source_index = 0; source_index < tile_set->get_source_count(); source_index++) {
+ int source_id = tile_set->get_source_id(source_index);
+ Ref<TileSetSource> source = tile_set->get_source(source_id);
+
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ for (int tile_index = 0; tile_index < source->get_tiles_count(); tile_index++) {
+ Vector2i tile_id = source->get_tile_id(tile_index);
+ for (int alternative_index = 0; alternative_index < source->get_alternative_tiles_count(tile_id); alternative_index++) {
+ int alternative_id = source->get_alternative_tile_id(tile_id, alternative_index);
+
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_id, alternative_id));
+ int terrain_set = tile_data->get_terrain_set();
+ if (terrain_set >= 0) {
+ ERR_FAIL_INDEX(terrain_set, (int)per_terrain_terrains_tile_patterns.size());
+
+ TileMapCell cell;
+ cell.source_id = source_id;
+ cell.set_atlas_coords(tile_id);
+ cell.alternative_tile = alternative_id;
+
+ TerrainsTilePattern terrains_tile_pattern = _build_terrains_tile_pattern(tile_data);
+
+ // Terrain bits.
+ for (int i = 0; i < terrains_tile_pattern.size(); i++) {
+ int terrain = terrains_tile_pattern[i];
+ if (terrain >= 0 && terrain < (int)per_terrain_terrains_tile_patterns[terrain_set].size()) {
+ per_terrain_terrains_tile_patterns[terrain_set][terrain].insert(terrains_tile_pattern);
+ terrain_tiles[cell] = tile_data;
+ per_terrain_terrains_tile_patterns_tiles[terrain_set][terrains_tile_pattern].insert(cell);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Add the empty cell in the possible patterns and cells.
+ for (int i = 0; i < tile_set->get_terrain_sets_count(); i++) {
+ TerrainsTilePattern empty_pattern;
+ for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) {
+ if (tile_set->is_valid_peering_bit_terrain(i, TileSet::CellNeighbor(j))) {
+ empty_pattern.push_back(-1);
+ }
+ }
+
+ 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;
+ per_terrain_terrains_tile_patterns_tiles[i][empty_pattern].insert(empty_cell);
+ }
+}
+
+void TileMapEditorTerrainsPlugin::_update_terrains_tree() {
+ terrains_tree->clear();
+ terrains_tree->create_item();
+
+ 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;
+ }
+
+ // Fill in the terrain list.
+ for (int terrain_set_index = 0; terrain_set_index < tile_set->get_terrain_sets_count(); terrain_set_index++) {
+ // Add an item for the terrain set.
+ TreeItem *terrain_set_tree_item = terrains_tree->create_item();
+ String matches;
+ if (tile_set->get_terrain_set_mode(terrain_set_index) == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) {
+ terrain_set_tree_item->set_icon(0, get_theme_icon("TerrainMatchCornersAndSides", "EditorIcons"));
+ matches = String(TTR("Matches Corners and Sides"));
+ } else if (tile_set->get_terrain_set_mode(terrain_set_index) == TileSet::TERRAIN_MODE_MATCH_CORNERS) {
+ terrain_set_tree_item->set_icon(0, get_theme_icon("TerrainMatchCorners", "EditorIcons"));
+ matches = String(TTR("Matches Corners Only"));
+ } else {
+ terrain_set_tree_item->set_icon(0, get_theme_icon("TerrainMatchSides", "EditorIcons"));
+ matches = String(TTR("Matches Sides Only"));
+ }
+ terrain_set_tree_item->set_text(0, vformat("Terrain Set %d (%s)", terrain_set_index, matches));
+ terrain_set_tree_item->set_selectable(0, false);
+
+ for (int terrain_index = 0; terrain_index < tile_set->get_terrains_count(terrain_set_index); terrain_index++) {
+ // Compute the terrains_tile_pattern used for terrain preview (whenever possible).
+ TerrainsTilePattern terrains_tile_pattern;
+ int max_bit_count = -1;
+ for (Set<TerrainsTilePattern>::Element *E = per_terrain_terrains_tile_patterns[terrain_set_index][terrain_index].front(); E; E = E->next()) {
+ int count = 0;
+ for (int i = 0; i < E->get().size(); i++) {
+ if (int(E->get()[i]) == terrain_index) {
+ count++;
+ }
+ }
+ if (count > max_bit_count) {
+ terrains_tile_pattern = E->get();
+ max_bit_count = count;
+ }
+ }
+
+ // Get the preview.
+ Ref<Texture2D> icon;
+ Rect2 region;
+ if (max_bit_count >= 0) {
+ double max_probability = -1.0;
+ for (Set<TileMapCell>::Element *E = per_terrain_terrains_tile_patterns_tiles[terrain_set_index][terrains_tile_pattern].front(); E; E = E->next()) {
+ Ref<TileSetSource> source = tile_set->get_source(E->get().source_id);
+
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile));
+ if (tile_data->get_probability() > max_probability) {
+ icon = atlas_source->get_texture();
+ region = atlas_source->get_tile_texture_region(E->get().get_atlas_coords());
+ max_probability = tile_data->get_probability();
+ }
+ }
+ }
+ } else {
+ Ref<Image> image;
+ image.instance();
+ image->create(1, 1, false, Image::FORMAT_RGBA8);
+ image->set_pixel(0, 0, tile_set->get_terrain_color(terrain_set_index, terrain_index));
+ Ref<ImageTexture> image_texture;
+ image_texture.instance();
+ image_texture->create_from_image(image);
+ image_texture->set_size_override(Size2(32, 32) * EDSCALE);
+ icon = image_texture;
+ }
+
+ // Add the item to the terrain list.
+ TreeItem *terrain_tree_item = terrains_tree->create_item(terrain_set_tree_item);
+ terrain_tree_item->set_text(0, tile_set->get_terrain_name(terrain_set_index, terrain_index));
+ terrain_tree_item->set_icon_max_width(0, 32 * EDSCALE);
+ terrain_tree_item->set_icon(0, icon);
+ terrain_tree_item->set_icon_region(0, region);
+ Dictionary metadata_dict;
+ metadata_dict["terrain_set"] = terrain_set_index;
+ metadata_dict["terrain_id"] = terrain_index;
+ terrain_tree_item->set_metadata(0, metadata_dict);
+ }
+ }
+}
+
+void TileMapEditorTerrainsPlugin::_update_tiles_list() {
+ terrains_tile_list->clear();
+
+ 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;
+ }
+
+ TreeItem *selected_tree_item = terrains_tree->get_selected();
+ if (selected_tree_item && selected_tree_item->get_metadata(0)) {
+ Dictionary metadata_dict = selected_tree_item->get_metadata(0);
+ int selected_terrain_set = metadata_dict["terrain_set"];
+ int selected_terrain_id = metadata_dict["terrain_id"];
+ ERR_FAIL_INDEX(selected_terrain_set, (int)per_terrain_terrains_tile_patterns.size());
+ ERR_FAIL_INDEX(selected_terrain_id, (int)per_terrain_terrains_tile_patterns[selected_terrain_set].size());
+
+ // Sort the items in a map by the number of corresponding terrains.
+ Map<int, Set<TerrainsTilePattern>> sorted;
+ for (Set<TerrainsTilePattern>::Element *E = per_terrain_terrains_tile_patterns[selected_terrain_set][selected_terrain_id].front(); E; E = E->next()) {
+ // Count the number of matching sides/terrains.
+ int count = 0;
+
+ for (int i = 0; i < E->get().size(); i++) {
+ if (int(E->get()[i]) == selected_terrain_id) {
+ count++;
+ }
+ }
+ sorted[count].insert(E->get());
+ }
+
+ for (Map<int, Set<TerrainsTilePattern>>::Element *E_set = sorted.back(); E_set; E_set = E_set->prev()) {
+ for (Set<TerrainsTilePattern>::Element *E = E_set->get().front(); E; E = E->next()) {
+ TerrainsTilePattern terrains_tile_pattern = E->get();
+
+ // Get the icon.
+ Ref<Texture2D> icon;
+ Rect2 region;
+ bool transpose = false;
+
+ double max_probability = -1.0;
+ for (Set<TileMapCell>::Element *E_tile_map_cell = per_terrain_terrains_tile_patterns_tiles[selected_terrain_set][terrains_tile_pattern].front(); E_tile_map_cell; E_tile_map_cell = E_tile_map_cell->next()) {
+ Ref<TileSetSource> source = tile_set->get_source(E_tile_map_cell->get().source_id);
+
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E_tile_map_cell->get().get_atlas_coords(), E_tile_map_cell->get().alternative_tile));
+ if (tile_data->get_probability() > max_probability) {
+ icon = atlas_source->get_texture();
+ region = atlas_source->get_tile_texture_region(E_tile_map_cell->get().get_atlas_coords());
+ if (tile_data->get_flip_h()) {
+ region.position.x += region.size.x;
+ region.size.x = -region.size.x;
+ }
+ if (tile_data->get_flip_v()) {
+ region.position.y += region.size.y;
+ region.size.y = -region.size.y;
+ }
+ transpose = tile_data->get_transpose();
+ max_probability = tile_data->get_probability();
+ }
+ }
+ }
+
+ // Create the ItemList's item.
+ int item_index = terrains_tile_list->add_item("");
+ terrains_tile_list->set_item_icon(item_index, icon);
+ terrains_tile_list->set_item_icon_region(item_index, region);
+ terrains_tile_list->set_item_icon_transposed(item_index, transpose);
+ Dictionary list_metadata_dict;
+ list_metadata_dict["terrains_tile_pattern"] = terrains_tile_pattern;
+ terrains_tile_list->set_item_metadata(item_index, list_metadata_dict);
+ }
+ }
+ if (terrains_tile_list->get_item_count() > 0) {
+ terrains_tile_list->select(0);
+ }
+ }
+}
+
+void TileMapEditorTerrainsPlugin::edit(ObjectID p_tile_map_id) {
+ tile_map_id = p_tile_map_id;
+ _update_terrains_cache();
+ _update_terrains_tree();
+ _update_tiles_list();
+}
+
+TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() {
+ set_name("Terrains");
+
+ HSplitContainer *tilemap_tab_terrains = memnew(HSplitContainer);
+ tilemap_tab_terrains->set_h_size_flags(SIZE_EXPAND_FILL);
+ tilemap_tab_terrains->set_v_size_flags(SIZE_EXPAND_FILL);
+ add_child(tilemap_tab_terrains);
+
+ terrains_tree = memnew(Tree);
+ terrains_tree->set_h_size_flags(SIZE_EXPAND_FILL);
+ terrains_tree->set_stretch_ratio(0.25);
+ terrains_tree->set_custom_minimum_size(Size2i(70, 0) * EDSCALE);
+ terrains_tree->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
+ terrains_tree->set_hide_root(true);
+ terrains_tree->connect("item_selected", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_tiles_list));
+ tilemap_tab_terrains->add_child(terrains_tree);
+
+ terrains_tile_list = memnew(ItemList);
+ terrains_tile_list->set_h_size_flags(SIZE_EXPAND_FILL);
+ terrains_tile_list->set_max_columns(0);
+ terrains_tile_list->set_same_column_width(true);
+ terrains_tile_list->set_fixed_icon_size(Size2(30, 30) * EDSCALE);
+ terrains_tile_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
+ tilemap_tab_terrains->add_child(terrains_tile_list);
+
+ // --- Toolbar ---
+ toolbar = memnew(HBoxContainer);
+
+ HBoxContainer *tilemap_tiles_tools_buttons = memnew(HBoxContainer);
+
+ tool_buttons_group.instance();
+
+ paint_tool_button = memnew(Button);
+ paint_tool_button->set_flat(true);
+ paint_tool_button->set_toggle_mode(true);
+ paint_tool_button->set_button_group(tool_buttons_group);
+ paint_tool_button->set_pressed(true);
+ paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", "Paint", KEY_E));
+ paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_toolbar));
+ tilemap_tiles_tools_buttons->add_child(paint_tool_button);
+
+ toolbar->add_child(tilemap_tiles_tools_buttons);
+
+ // -- TileMap tool settings --
+ tools_settings = memnew(HBoxContainer);
+ toolbar->add_child(tools_settings);
+
+ tools_settings_vsep = memnew(VSeparator);
+ tools_settings->add_child(tools_settings_vsep);
+
+ // Picker
+ picker_button = memnew(Button);
+ picker_button->set_flat(true);
+ picker_button->set_toggle_mode(true);
+ picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P));
+ picker_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport));
+ tools_settings->add_child(picker_button);
+
+ // Erase button.
+ erase_button = memnew(Button);
+ erase_button->set_flat(true);
+ erase_button->set_toggle_mode(true);
+ erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", KEY_E));
+ erase_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport));
+ tools_settings->add_child(erase_button);
+}
+
+TileMapEditorTerrainsPlugin::~TileMapEditorTerrainsPlugin() {
+}
+
+void TileMapEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED:
+ missing_tile_texture = get_theme_icon("StatusWarning", "EditorIcons");
+ warning_pattern_texture = get_theme_icon("WarningPattern", "EditorIcons");
+ break;
+ case NOTIFICATION_INTERNAL_PROCESS:
+ if (is_visible_in_tree() && tileset_changed_needs_update) {
+ _update_bottom_panel();
+ tile_map_editor_plugins[tabs->get_current_tab()]->tile_set_changed();
+ CanvasItemEditor::get_singleton()->update_viewport();
+ tileset_changed_needs_update = false;
+ }
+ break;
+ }
+}
+
+void TileMapEditor::_update_bottom_panel() {
+ 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();
+
+ // Update the visibility of controls.
+ missing_tileset_label->set_visible(!tile_set.is_valid());
+ if (!tile_set.is_valid()) {
+ for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
+ tile_map_editor_plugins[i]->hide();
+ }
+ } else {
+ for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
+ tile_map_editor_plugins[i]->set_visible(i == tabs->get_current_tab());
+ }
+ }
+}
+
+Vector<Vector2i> TileMapEditor::get_line(TileMap *p_tile_map, Vector2i p_from_cell, Vector2i p_to_cell) {
+ ERR_FAIL_COND_V(!p_tile_map, Vector<Vector2i>());
+
+ Ref<TileSet> tile_set = p_tile_map->get_tileset();
+ ERR_FAIL_COND_V(!tile_set.is_valid(), Vector<Vector2i>());
+
+ if (tile_set->get_tile_shape() == TileSet::TILE_SHAPE_SQUARE) {
+ return Geometry2D::bresenham_line(p_from_cell, p_to_cell);
+ } else {
+ // Adapt the bresenham line algorithm to half-offset shapes.
+ // See this blog post: http://zvold.blogspot.com/2010/01/bresenhams-line-drawing-algorithm-on_26.html
+ Vector<Point2i> points;
+
+ bool transposed = tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL;
+ p_from_cell = TileMap::transform_coords_layout(p_from_cell, tile_set->get_tile_offset_axis(), tile_set->get_tile_layout(), TileSet::TILE_LAYOUT_STACKED);
+ p_to_cell = TileMap::transform_coords_layout(p_to_cell, tile_set->get_tile_offset_axis(), tile_set->get_tile_layout(), TileSet::TILE_LAYOUT_STACKED);
+ if (transposed) {
+ SWAP(p_from_cell.x, p_from_cell.y);
+ SWAP(p_to_cell.x, p_to_cell.y);
+ }
+
+ Vector2i delta = p_to_cell - p_from_cell;
+ delta = Vector2i(2 * delta.x + ABS(p_to_cell.y % 2) - ABS(p_from_cell.y % 2), delta.y);
+ Vector2i sign = delta.sign();
+
+ Vector2i current = p_from_cell;
+ points.push_back(TileMap::transform_coords_layout(transposed ? Vector2i(current.y, current.x) : current, tile_set->get_tile_offset_axis(), TileSet::TILE_LAYOUT_STACKED, tile_set->get_tile_layout()));
+
+ int err = 0;
+ if (ABS(delta.y) < ABS(delta.x)) {
+ Vector2i err_step = 3 * delta.abs();
+ while (current != p_to_cell) {
+ err += err_step.y;
+ if (err > ABS(delta.x)) {
+ if (sign.x == 0) {
+ current += Vector2(sign.y, 0);
+ } else {
+ current += Vector2(bool(current.y % 2) ^ (sign.x < 0) ? sign.x : 0, sign.y);
+ }
+ err -= err_step.x;
+ } else {
+ current += Vector2i(sign.x, 0);
+ err += err_step.y;
+ }
+ points.push_back(TileMap::transform_coords_layout(transposed ? Vector2i(current.y, current.x) : current, tile_set->get_tile_offset_axis(), TileSet::TILE_LAYOUT_STACKED, tile_set->get_tile_layout()));
+ }
+ } else {
+ Vector2i err_step = delta.abs();
+ while (current != p_to_cell) {
+ err += err_step.x;
+ if (err > 0) {
+ if (sign.x == 0) {
+ current += Vector2(0, sign.y);
+ } else {
+ current += Vector2(bool(current.y % 2) ^ (sign.x < 0) ? sign.x : 0, sign.y);
+ }
+ err -= err_step.y;
+ } else {
+ if (sign.x == 0) {
+ current += Vector2(0, sign.y);
+ } else {
+ current += Vector2(bool(current.y % 2) ^ (sign.x > 0) ? -sign.x : 0, sign.y);
+ }
+ err += err_step.y;
+ }
+ points.push_back(TileMap::transform_coords_layout(transposed ? Vector2i(current.y, current.x) : current, tile_set->get_tile_offset_axis(), TileSet::TILE_LAYOUT_STACKED, tile_set->get_tile_layout()));
+ }
+ }
+
+ return points;
+ }
+}
+
+void TileMapEditor::_tile_map_changed() {
+ tileset_changed_needs_update = true;
+}
+
+void TileMapEditor::_tab_changed(int p_tab_id) {
+ // Make the plugin edit the correct tilemap.
+ tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id);
+
+ // Update toolbar.
+ for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
+ tile_map_editor_plugins[i]->get_toolbar()->set_visible(i == p_tab_id);
+ }
+
+ // Update visible panel.
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map || !tile_map->get_tileset().is_valid()) {
+ for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
+ tile_map_editor_plugins[i]->hide();
+ }
+ } else {
+ for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
+ tile_map_editor_plugins[i]->set_visible(i == tabs->get_current_tab());
+ }
+ }
+
+ // Graphical update.
+ tile_map_editor_plugins[tabs->get_current_tab()]->update();
+ CanvasItemEditor::get_singleton()->update_viewport();
+}
+
+bool TileMapEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
+ return tile_map_editor_plugins[tabs->get_current_tab()]->forward_canvas_gui_input(p_event);
+}
+
+void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
+ 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;
+ }
+
+ if (!tile_map->is_visible_in_tree()) {
+ return;
+ }
+
+ Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform();
+ Transform2D xform_inv = xform.affine_inverse();
+ Vector2i tile_shape_size = tile_set->get_tile_size();
+
+ // Draw tiles with invalid IDs in the grid.
+ float icon_ratio = MIN(missing_tile_texture->get_size().x / tile_set->get_tile_size().x, missing_tile_texture->get_size().y / tile_set->get_tile_size().y) / 3;
+ TypedArray<Vector2i> used_cells = tile_map->get_used_cells();
+ for (int i = 0; i < used_cells.size(); i++) {
+ Vector2i coords = used_cells[i];
+ int tile_source_id = tile_map->get_cell_source_id(coords);
+ if (tile_source_id >= 0) {
+ Vector2i tile_atlas_coords = tile_map->get_cell_atlas_coords(coords);
+ int tile_alternative_tile = tile_map->get_cell_alternative_tile(coords);
+
+ TileSetSource *source = nullptr;
+ if (tile_set->has_source(tile_source_id)) {
+ source = *tile_set->get_source(tile_source_id);
+ }
+
+ if (!source || !source->has_tile(tile_atlas_coords) || !source->has_alternative_tile(tile_atlas_coords, tile_alternative_tile)) {
+ // Generate a random color from the hashed values of the tiles.
+ Array to_hash;
+ to_hash.push_back(tile_source_id);
+ to_hash.push_back(tile_atlas_coords);
+ to_hash.push_back(tile_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 the scaled tile.
+ Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(coords) - Vector2(tile_shape_size) / 2, Vector2(tile_shape_size)));
+ tile_set->draw_tile_shape(p_overlay, cell_region, color, true, warning_pattern_texture);
+
+ // Draw the warning icon.
+ Rect2 rect = Rect2(xform.xform(tile_map->map_to_world(coords)) - (icon_ratio * missing_tile_texture->get_size() * xform.get_scale() / 2), icon_ratio * missing_tile_texture->get_size() * xform.get_scale());
+ p_overlay->draw_texture_rect(missing_tile_texture, rect);
+ }
+ }
+ }
+
+ // Fading on the border.
+ const int fading = 5;
+
+ // Determine the drawn area.
+ Size2 screen_size = p_overlay->get_size();
+ Rect2i screen_rect;
+ screen_rect.position = tile_map->world_to_map(xform_inv.xform(Vector2()));
+ screen_rect.expand_to(tile_map->world_to_map(xform_inv.xform(Vector2(0, screen_size.height))));
+ screen_rect.expand_to(tile_map->world_to_map(xform_inv.xform(Vector2(screen_size.width, 0))));
+ screen_rect.expand_to(tile_map->world_to_map(xform_inv.xform(screen_size)));
+ screen_rect = screen_rect.grow(1);
+
+ Rect2i tilemap_used_rect = tile_map->get_used_rect();
+
+ Rect2i displayed_rect = tilemap_used_rect.intersection(screen_rect);
+ displayed_rect = displayed_rect.grow(fading);
+
+ // Reduce the drawn area to avoid crashes if needed.
+ int max_size = 100;
+ if (displayed_rect.size.x > max_size) {
+ displayed_rect = displayed_rect.grow_individual(-(displayed_rect.size.x - max_size) / 2, 0, -(displayed_rect.size.x - max_size) / 2, 0);
+ }
+ if (displayed_rect.size.y > max_size) {
+ displayed_rect = displayed_rect.grow_individual(0, -(displayed_rect.size.y - max_size) / 2, 0, -(displayed_rect.size.y - max_size) / 2);
+ }
+
+ // Draw the grid.
+ for (int x = displayed_rect.position.x; x < (displayed_rect.position.x + displayed_rect.size.x); x++) {
+ for (int y = displayed_rect.position.y; y < (displayed_rect.position.y + displayed_rect.size.y); y++) {
+ Vector2i pos_in_rect = Vector2i(x, y) - displayed_rect.position;
+
+ // Fade out the border of the grid.
+ float left_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.x), 0.0f, 1.0f);
+ float right_opacity = CLAMP(Math::inverse_lerp((float)displayed_rect.size.x, (float)(displayed_rect.size.x - fading), (float)pos_in_rect.x), 0.0f, 1.0f);
+ float top_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.y), 0.0f, 1.0f);
+ float bottom_opacity = CLAMP(Math::inverse_lerp((float)displayed_rect.size.y, (float)(displayed_rect.size.y - fading), (float)pos_in_rect.y), 0.0f, 1.0f);
+ float opacity = CLAMP(MIN(left_opacity, MIN(right_opacity, MIN(top_opacity, bottom_opacity))) + 0.1, 0.0f, 1.0f);
+
+ Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(Vector2(x, y)) - tile_shape_size / 2, tile_shape_size));
+ tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 0.5, 0.2, 0.5 * opacity), false);
+ }
+ }
+
+ // Draw the IDs for debug.
+ /*Ref<Font> font = get_theme_font("font", "Label");
+ for (int x = displayed_rect.position.x; x < (displayed_rect.position.x + displayed_rect.size.x); x++) {
+ for (int y = displayed_rect.position.y; y < (displayed_rect.position.y + displayed_rect.size.y); y++) {
+ p_overlay->draw_string(font, xform.xform(tile_map->map_to_world(Vector2(x, y))) + Vector2i(-tile_shape_size.x / 2, 0), vformat("%s", Vector2(x, y)));
+ }
+ }*/
+
+ // Draw the plugins.
+ tile_map_editor_plugins[tabs->get_current_tab()]->forward_canvas_draw_over_viewport(p_overlay);
+}
+
+void TileMapEditor::edit(TileMap *p_tile_map) {
+ if (p_tile_map && p_tile_map->get_instance_id() == tile_map_id) {
+ return;
+ }
+
+ // Disconnect to changes.
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (tile_map) {
+ tile_map->disconnect("changed", callable_mp(this, &TileMapEditor::_tile_map_changed));
+ }
+
+ // Change the edited object.
+ if (p_tile_map) {
+ tile_map_id = p_tile_map->get_instance_id();
+ tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ // Connect to changes.
+ if (!tile_map->is_connected("changed", callable_mp(this, &TileMapEditor::_tile_map_changed))) {
+ tile_map->connect("changed", callable_mp(this, &TileMapEditor::_tile_map_changed));
+ }
+ } else {
+ tile_map_id = ObjectID();
+ }
+
+ // Call the plugins.
+ tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id);
+
+ _tile_map_changed();
+}
+
+TileMapEditor::TileMapEditor() {
+ set_process_internal(true);
+
+ // TileMap editor plugins
+ tile_map_editor_plugins.push_back(memnew(TileMapEditorTilesPlugin));
+ tile_map_editor_plugins.push_back(memnew(TileMapEditorTerrainsPlugin));
+
+ // Tabs.
+ tabs = memnew(Tabs);
+ tabs->set_clip_tabs(false);
+ for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
+ tabs->add_tab(tile_map_editor_plugins[i]->get_name());
+ }
+ tabs->connect("tab_changed", callable_mp(this, &TileMapEditor::_tab_changed));
+
+ // --- TileMap toolbar ---
+ tilemap_toolbar = memnew(HBoxContainer);
+ //tilemap_toolbar->add_child(memnew(VSeparator));
+ tilemap_toolbar->add_child(tabs);
+ //tilemap_toolbar->add_child(memnew(VSeparator));
+ for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
+ tile_map_editor_plugins[i]->get_toolbar()->hide();
+ tilemap_toolbar->add_child(tile_map_editor_plugins[i]->get_toolbar());
+ }
+
+ missing_tileset_label = memnew(Label);
+ missing_tileset_label->set_text(TTR("The edited TileMap node has no TileSet resource."));
+ missing_tileset_label->set_h_size_flags(SIZE_EXPAND_FILL);
+ missing_tileset_label->set_v_size_flags(SIZE_EXPAND_FILL);
+ missing_tileset_label->set_align(Label::ALIGN_CENTER);
+ missing_tileset_label->set_valign(Label::VALIGN_CENTER);
+ missing_tileset_label->hide();
+ add_child(missing_tileset_label);
+
+ for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
+ add_child(tile_map_editor_plugins[i]);
+ tile_map_editor_plugins[i]->set_h_size_flags(SIZE_EXPAND_FILL);
+ tile_map_editor_plugins[i]->set_v_size_flags(SIZE_EXPAND_FILL);
+ tile_map_editor_plugins[i]->set_visible(i == 0);
+ }
+
+ _tab_changed(0);
+}
+
+TileMapEditor::~TileMapEditor() {
+}
diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h
new file mode 100644
index 0000000000..f780686b82
--- /dev/null
+++ b/editor/plugins/tiles/tile_map_editor.h
@@ -0,0 +1,328 @@
+/*************************************************************************/
+/* tile_map_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_MAP_EDITOR_H
+#define TILE_MAP_EDITOR_H
+
+#include "tile_atlas_view.h"
+
+#include "core/typedefs.h"
+#include "editor/editor_node.h"
+#include "scene/2d/tile_map.h"
+#include "scene/gui/box_container.h"
+#include "scene/gui/tabs.h"
+
+class TileMapEditorPlugin : public VBoxContainer {
+public:
+ virtual Control *get_toolbar() const {
+ return memnew(Control);
+ };
+ virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) { return false; };
+ virtual void forward_canvas_draw_over_viewport(Control *p_overlay){};
+ virtual void tile_set_changed(){};
+ virtual void edit(ObjectID p_tile_map_id){};
+};
+
+class TileMapEditorTilesPlugin : public TileMapEditorPlugin {
+ GDCLASS(TileMapEditorTilesPlugin, TileMapEditorPlugin);
+
+private:
+ UndoRedo *undo_redo = EditorNode::get_undo_redo();
+ ObjectID tile_map_id;
+ virtual void edit(ObjectID p_tile_map_id) override;
+
+ // Toolbar.
+ HBoxContainer *toolbar;
+
+ Ref<ButtonGroup> tool_buttons_group;
+ Button *select_tool_button;
+ Button *paint_tool_button;
+ Button *line_tool_button;
+ Button *rect_tool_button;
+ Button *bucket_tool_button;
+ Button *picker_button;
+
+ HBoxContainer *tools_settings;
+ VSeparator *tools_settings_vsep;
+ Button *erase_button;
+ CheckBox *bucket_continuous_checkbox;
+
+ VSeparator *tools_settings_vsep_2;
+ CheckBox *random_tile_checkbox;
+ float scattering = 0.0;
+ Label *scatter_label;
+ SpinBox *scatter_spinbox;
+ void _on_random_tile_checkbox_toggled(bool p_pressed);
+ void _on_scattering_spinbox_changed(double p_value);
+
+ void _update_toolbar();
+
+ // Tilemap editing.
+ bool has_mouse = false;
+ void _mouse_exited_viewport();
+
+ enum DragType {
+ DRAG_TYPE_NONE = 0,
+ DRAG_TYPE_SELECT,
+ DRAG_TYPE_MOVE,
+ DRAG_TYPE_PAINT,
+ DRAG_TYPE_LINE,
+ DRAG_TYPE_RECT,
+ DRAG_TYPE_BUCKET,
+ DRAG_TYPE_PICK,
+ DRAG_TYPE_CLIPBOARD_PASTE,
+ };
+ DragType drag_type = DRAG_TYPE_NONE;
+ Vector2 drag_start_mouse_pos;
+ Vector2 drag_last_mouse_pos;
+ 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_bucket_fill(Vector2i p_coords, bool p_contiguous);
+ void _stop_dragging();
+
+ // Selection system.
+ Set<Vector2i> tile_map_selection;
+ TileMapPattern *tile_map_clipboard = memnew(TileMapPattern);
+ TileMapPattern *selection_pattern = memnew(TileMapPattern);
+ void _set_tile_map_selection(const TypedArray<Vector2i> &p_selection);
+ TypedArray<Vector2i> _get_tile_map_selection() const;
+
+ Set<TileMapCell> tile_set_selection;
+
+ void _update_selection_pattern_from_tilemap_selection();
+ void _update_selection_pattern_from_tileset_selection();
+ 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;
+
+ Label *missing_source_label;
+ HSplitContainer *atlas_sources_split_container;
+
+ ItemList *sources_list;
+ TileAtlasView *tile_atlas_view;
+ Ref<Texture2D> missing_texture_texture;
+ void _update_tile_set_sources_list();
+ void _update_atlas_view();
+
+ void _update_bottom_panel();
+
+ TileMapCell hovered_tile;
+
+ Control *tile_atlas_control;
+ void _tile_atlas_control_mouse_exited();
+ void _tile_atlas_control_gui_input(const Ref<InputEvent> &p_event);
+ void _tile_atlas_control_draw();
+
+ Control *alternative_tiles_control;
+ void _tile_alternatives_control_draw();
+ void _tile_alternatives_control_mouse_exited();
+ void _tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event);
+
+ // Update callback
+ virtual void tile_set_changed() override;
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ virtual Control *get_toolbar() const override;
+ virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override;
+ virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override;
+
+ TileMapEditorTilesPlugin();
+ ~TileMapEditorTilesPlugin();
+};
+
+class TileMapEditorTerrainsPlugin : public TileMapEditorPlugin {
+ GDCLASS(TileMapEditorTerrainsPlugin, TileMapEditorPlugin);
+
+private:
+ UndoRedo *undo_redo = EditorNode::get_undo_redo();
+ ObjectID tile_map_id;
+ virtual void edit(ObjectID p_tile_map_id) override;
+
+ // Toolbar.
+ HBoxContainer *toolbar;
+
+ Ref<ButtonGroup> tool_buttons_group;
+ Button *paint_tool_button;
+
+ HBoxContainer *tools_settings;
+ VSeparator *tools_settings_vsep;
+ Button *picker_button;
+ Button *erase_button;
+
+ void _update_toolbar();
+
+ // TileMap editing.
+ enum DragType {
+ DRAG_TYPE_NONE = 0,
+ DRAG_TYPE_PAINT,
+ DRAG_TYPE_PICK,
+ };
+ DragType drag_type = DRAG_TYPE_NONE;
+ Vector2 drag_start_mouse_pos;
+ Vector2 drag_last_mouse_pos;
+ Map<Vector2i, TileMapCell> drag_modified;
+
+ // Painting
+ class Constraint {
+ private:
+ const TileMap *tile_map;
+ Vector2i base_cell_coords = Vector2i();
+ int bit = -1;
+ int terrain = -1;
+
+ public:
+ // TODO implement difference operator.
+ bool operator<(const Constraint &p_other) const {
+ if (base_cell_coords == p_other.base_cell_coords) {
+ return bit < p_other.bit;
+ }
+ return base_cell_coords < p_other.base_cell_coords;
+ }
+
+ String to_string() const {
+ return vformat("Constraint {pos:%s, bit:%d, terrain:%d}", base_cell_coords, bit, terrain);
+ }
+
+ Vector2i get_base_cell_coords() const {
+ return base_cell_coords;
+ }
+
+ Map<Vector2i, TileSet::CellNeighbor> get_overlapping_coords_and_peering_bits() const;
+
+ void set_terrain(int p_terrain) {
+ terrain = p_terrain;
+ }
+
+ int get_terrain() const {
+ return terrain;
+ }
+
+ Constraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain);
+ Constraint() {}
+ };
+
+ typedef Array TerrainsTilePattern;
+
+ Set<TerrainsTilePattern> _get_valid_terrains_tile_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<TileMapEditorTerrainsPlugin::Constraint> p_constraints) const;
+ Set<TileMapEditorTerrainsPlugin::Constraint> _get_constraints_from_removed_cells_list(const Set<Vector2i> &p_to_replace, int p_terrain_set) const;
+ Set<TileMapEditorTerrainsPlugin::Constraint> _get_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TerrainsTilePattern p_terrains_tile_pattern) const;
+ Map<Vector2i, TerrainsTilePattern> _wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TileMapEditorTerrainsPlugin::Constraint> p_constraints) const;
+ TileMapCell _get_random_tile_from_pattern(int p_terrain_set, TerrainsTilePattern p_terrain_tile_pattern) const;
+ Map<Vector2i, TileMapCell> _draw_terrains(const Map<Vector2i, TerrainsTilePattern> &p_to_paint, int p_terrain_set) const;
+
+ // Cached data.
+
+ TerrainsTilePattern _build_terrains_tile_pattern(TileData *p_tile_data);
+ LocalVector<Map<TerrainsTilePattern, Set<TileMapCell>>> per_terrain_terrains_tile_patterns_tiles;
+ LocalVector<LocalVector<Set<TerrainsTilePattern>>> per_terrain_terrains_tile_patterns;
+
+ Map<TileMapCell, TileData *> terrain_tiles;
+ LocalVector<TileSet::CellNeighbor> tile_sides;
+
+ // Bottom panel.
+ Tree *terrains_tree;
+ ItemList *terrains_tile_list;
+
+ // Update functions.
+ void _update_terrains_cache();
+ void _update_terrains_tree();
+ void _update_tiles_list();
+
+ // Update callback
+ virtual void tile_set_changed() override;
+
+protected:
+ void _notification(int p_what);
+ // static void _bind_methods();
+
+public:
+ virtual Control *get_toolbar() const override;
+ virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override;
+ //virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override;
+
+ TileMapEditorTerrainsPlugin();
+ ~TileMapEditorTerrainsPlugin();
+};
+
+class TileMapEditor : public VBoxContainer {
+ GDCLASS(TileMapEditor, VBoxContainer);
+
+private:
+ bool tileset_changed_needs_update = false;
+ ObjectID tile_map_id;
+
+ // Vector to keep plugins.
+ Vector<TileMapEditorPlugin *> tile_map_editor_plugins;
+
+ // Toolbar.
+ HBoxContainer *tilemap_toolbar;
+
+ // Bottom panel
+ Label *missing_tileset_label;
+ Tabs *tabs;
+ void _update_bottom_panel();
+
+ // TileMap
+ Ref<Texture2D> missing_tile_texture;
+ Ref<Texture2D> warning_pattern_texture;
+
+ // CallBack
+ void _tile_map_changed();
+ void _tab_changed(int p_tab_changed);
+
+protected:
+ void _notification(int p_what);
+ void _draw_shape(Control *p_control, Rect2 p_region, TileSet::TileShape p_shape, TileSet::TileOffsetAxis p_offset_axis, Color p_color);
+
+public:
+ bool forward_canvas_gui_input(const Ref<InputEvent> &p_event);
+ void forward_canvas_draw_over_viewport(Control *p_overlay);
+
+ void edit(TileMap *p_tile_map);
+ Control *get_toolbar() { return tilemap_toolbar; };
+
+ TileMapEditor();
+ ~TileMapEditor();
+
+ // Static functions.
+ static Vector<Vector2i> get_line(TileMap *p_tile_map, Vector2i p_from_cell, Vector2i p_to_cell);
+};
+
+#endif // TILE_MAP_EDITOR_PLUGIN_H
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
new file mode 100644
index 0000000000..69abbb29f1
--- /dev/null
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
@@ -0,0 +1,1858 @@
+/*************************************************************************/
+/* tile_set_atlas_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_atlas_source_editor.h"
+
+#include "tiles_editor_plugin.h"
+
+#include "editor/editor_inspector.h"
+#include "editor/editor_scale.h"
+#include "editor/progress_dialog.h"
+
+#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+#include "scene/gui/control.h"
+#include "scene/gui/item_list.h"
+#include "scene/gui/separator.h"
+#include "scene/gui/split_container.h"
+#include "scene/gui/tab_container.h"
+
+#include "core/core_string_names.h"
+#include "core/math/geometry_2d.h"
+#include "core/os/keyboard.h"
+
+void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::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 atlas source ID. Another atlas 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.
+ tile_set->set_source_id(previous_source, p_id);
+ emit_signal("changed", "id");
+}
+
+int TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::get_id() {
+ return source_id;
+}
+
+bool TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_set(const StringName &p_name, const Variant &p_value) {
+ bool valid = false;
+ tile_set_atlas_source->set(p_name, p_value, &valid);
+ if (valid) {
+ emit_signal("changed", String(p_name).utf8().get_data());
+ }
+ return valid;
+}
+
+bool TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_get(const StringName &p_name, Variant &r_ret) const {
+ if (!tile_set_atlas_source) {
+ return false;
+ }
+ bool valid = false;
+ r_ret = tile_set_atlas_source->get(p_name, &valid);
+ return valid;
+}
+
+void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_get_property_list(List<PropertyInfo> *p_list) const {
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2I, "margins", PROPERTY_HINT_NONE, ""));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, ""));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2I, "tile_size", PROPERTY_HINT_NONE, ""));
+}
+
+void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_bind_methods() {
+ // -- Shape and layout --
+ ClassDB::bind_method(D_METHOD("set_id", "id"), &TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::set_id);
+ ClassDB::bind_method(D_METHOD("get_id"), &TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::get_id);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "id"), "set_id", "get_id");
+
+ ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what")));
+}
+
+void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id) {
+ ERR_FAIL_COND(!p_tile_set.is_valid());
+ ERR_FAIL_COND(!p_tile_set_atlas_source);
+ ERR_FAIL_COND(p_source_id < 0);
+ ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source);
+
+ // Disconnect to changes.
+ if (tile_set_atlas_source) {
+ tile_set_atlas_source->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed));
+ }
+
+ tile_set = p_tile_set;
+ tile_set_atlas_source = p_tile_set_atlas_source;
+ source_id = p_source_id;
+
+ // Connect to changes.
+ if (tile_set_atlas_source) {
+ if (!tile_set_atlas_source->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) {
+ tile_set_atlas_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 TileSetAtlasSourceEditor::TileProxyObject::_set(const StringName &p_name, const Variant &p_value) {
+ if (!tile_set_atlas_source) {
+ return false;
+ }
+
+ if (tiles.size() == 1) {
+ const Vector2i &coords = tiles.front()->get().tile;
+ const int &alternative = tiles.front()->get().alternative;
+
+ if (alternative == 0 && p_name == "atlas_coords") {
+ Vector2i as_vector2i = Vector2i(p_value);
+ ERR_FAIL_COND_V(!tile_set_atlas_source->can_move_tile_in_atlas(coords, as_vector2i), false);
+
+ if (tiles_set_atlas_source_editor->selection.front()->get().tile == coords) {
+ tiles_set_atlas_source_editor->selection.clear();
+ tiles_set_atlas_source_editor->selection.insert({ as_vector2i, 0 });
+ tiles_set_atlas_source_editor->_update_tile_id_label();
+ }
+
+ tile_set_atlas_source->move_tile_in_atlas(coords, as_vector2i);
+ tiles.clear();
+ tiles.insert({ as_vector2i, 0 });
+ emit_signal("changed", "atlas_coords");
+ 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);
+
+ tile_set_atlas_source->move_tile_in_atlas(coords, TileSetAtlasSource::INVALID_ATLAS_COORDS, as_vector2i);
+ emit_signal("changed", "size_in_atlas");
+ return true;
+ } else if (alternative > 0 && p_name == "alternative_id") {
+ int as_int = int(p_value);
+ ERR_FAIL_COND_V(as_int < 0, false);
+ ERR_FAIL_COND_V_MSG(tile_set_atlas_source->has_alternative_tile(coords, as_int), false, vformat("Cannot change alternative tile ID. Another alternative exists with id %d for tile at coords %s.", as_int, coords));
+
+ if (tiles_set_atlas_source_editor->selection.front()->get().alternative == alternative) {
+ tiles_set_atlas_source_editor->selection.clear();
+ tiles_set_atlas_source_editor->selection.insert({ coords, as_int });
+ }
+
+ int previous_alternative_tile = alternative;
+ tiles.clear();
+ tiles.insert({ coords, as_int }); // tiles must be updated before.
+ tile_set_atlas_source->set_alternative_tile_id(coords, previous_alternative_tile, as_int);
+
+ emit_signal("changed", "alternative_id");
+ return true;
+ }
+ }
+
+ bool any_valid = false;
+ for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) {
+ const Vector2i &coords = E->get().tile;
+ const int &alternative = E->get().alternative;
+
+ bool valid = false;
+ TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative));
+ ERR_FAIL_COND_V(!tile_data, false);
+ tile_data->set(p_name, p_value, &valid);
+
+ any_valid |= valid;
+ }
+
+ if (any_valid) {
+ emit_signal("changed", String(p_name).utf8().get_data());
+ }
+
+ return any_valid;
+}
+
+bool TileSetAtlasSourceEditor::TileProxyObject::_get(const StringName &p_name, Variant &r_ret) const {
+ if (!tile_set_atlas_source) {
+ return false;
+ }
+
+ if (tiles.size() == 1) {
+ const Vector2i &coords = tiles.front()->get().tile;
+ const int &alternative = tiles.front()->get().alternative;
+
+ if (alternative == 0 && p_name == "atlas_coords") {
+ r_ret = coords;
+ return true;
+ } else if (alternative == 0 && p_name == "size_in_atlas") {
+ r_ret = tile_set_atlas_source->get_tile_size_in_atlas(coords);
+ return true;
+ } else if (alternative > 0 && p_name == "alternative_id") {
+ r_ret = alternative;
+ return true;
+ }
+ }
+
+ for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) {
+ // Return the first tile with a property matching the name.
+ // Note: It's a little bit annoying, but the behavior is the same the one in MultiNodeEdit.
+ const Vector2i &coords = E->get().tile;
+ const int &alternative = E->get().alternative;
+
+ TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative));
+ ERR_FAIL_COND_V(!tile_data, false);
+
+ bool valid = false;
+ r_ret = tile_data->get(p_name, &valid);
+ if (valid) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void TileSetAtlasSourceEditor::TileProxyObject::_get_property_list(List<PropertyInfo> *p_list) const {
+ if (!tile_set_atlas_source) {
+ return;
+ }
+
+ if (tiles.size() == 1) {
+ if (tiles.front()->get().alternative == 0) {
+ p_list->push_back(PropertyInfo(Variant::VECTOR2I, "atlas_coords", PROPERTY_HINT_NONE, ""));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2I, "size_in_atlas", PROPERTY_HINT_NONE, ""));
+ } else {
+ p_list->push_back(PropertyInfo(Variant::INT, "alternative_id", PROPERTY_HINT_NONE, ""));
+ }
+ }
+
+ // Get the list of properties common to all tiles (similar to what's done in MultiNodeEdit).
+ struct PropertyId {
+ int occurence_id = 0;
+ String property;
+ bool operator<(const PropertyId &p_other) const {
+ return occurence_id == p_other.occurence_id ? property < p_other.property : occurence_id < p_other.occurence_id;
+ }
+ };
+ struct PLData {
+ int uses = 0;
+ PropertyInfo property_info;
+ };
+ Map<PropertyId, PLData> usage;
+
+ List<PLData *> data_list;
+ for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) {
+ const Vector2i &coords = E->get().tile;
+ const int &alternative = E->get().alternative;
+
+ TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative));
+ ERR_FAIL_COND(!tile_data);
+
+ List<PropertyInfo> list;
+ tile_data->get_property_list(&list);
+
+ Map<String, int> counts; // Counts the number of time a property appears (useful for groups that may appear more than once)
+ for (List<PropertyInfo>::Element *E_property = list.front(); E_property; E_property = E_property->next()) {
+ const String &property_string = E_property->get().name;
+ if (!tile_data->is_allowing_transform() && (property_string == "flip_h" || property_string == "flip_v" || property_string == "transpose")) {
+ continue;
+ }
+
+ if (!counts.has(property_string)) {
+ counts[property_string] = 1;
+ } else {
+ counts[property_string] += 1;
+ }
+
+ PropertyInfo stored_property_info = E_property->get();
+ stored_property_info.usage |= PROPERTY_USAGE_STORAGE; // Ignore the storage flag in comparing properties.
+
+ PropertyId id = { counts[property_string], property_string };
+ if (!usage.has(id)) {
+ usage[id] = { 1, stored_property_info };
+ data_list.push_back(&usage[id]);
+ } else if (usage[id].property_info == stored_property_info) {
+ usage[id].uses += 1;
+ }
+ }
+ }
+
+ // Add only properties that are common to all tiles.
+ for (List<PLData *>::Element *E = data_list.front(); E; E = E->next()) {
+ if (E->get()->uses == tiles.size()) {
+ p_list->push_back(E->get()->property_info);
+ }
+ }
+}
+
+void TileSetAtlasSourceEditor::TileProxyObject::edit(TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id, 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().alternative < 0);
+ }
+
+ // Disconnect to changes.
+ for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) {
+ const Vector2i &coords = E->get().tile;
+ const int &alternative = E->get().alternative;
+
+ if (tile_set_atlas_source && tile_set_atlas_source->has_tile(coords) && tile_set_atlas_source->has_alternative_tile(coords, alternative)) {
+ TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative));
+ if (tile_data->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) {
+ tile_data->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed));
+ }
+ }
+ }
+
+ tile_set_atlas_source = p_tile_set_atlas_source;
+ source_id = p_source_id;
+ tiles = Set<TileSelection>(p_tiles);
+
+ // Connect to changes.
+ for (Set<TileSelection>::Element *E = p_tiles.front(); E; E = E->next()) {
+ const Vector2i &coords = E->get().tile;
+ const int &alternative = E->get().alternative;
+
+ if (tile_set_atlas_source->has_tile(coords) && tile_set_atlas_source->has_alternative_tile(coords, alternative)) {
+ TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative));
+ if (!tile_data->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) {
+ tile_data->connect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed));
+ }
+ }
+ }
+
+ notify_property_list_changed();
+}
+
+void TileSetAtlasSourceEditor::TileProxyObject::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what")));
+}
+
+void TileSetAtlasSourceEditor::_inspector_property_selected(String p_property) {
+ selected_property = p_property;
+ _update_atlas_view();
+}
+
+void TileSetAtlasSourceEditor::_update_tile_id_label() {
+ if (selection.size() == 1) {
+ TileSelection selected = selection.front()->get();
+ tool_tile_id_label->set_text(vformat("%d, %s, %d", tile_set_atlas_source_id, selected.tile, selected.alternative));
+ tool_tile_id_label->set_tooltip(vformat(TTR("Selected tile:\nSource: %d\nAtlas coordinates: %s\nAlternative: %d"), tile_set_atlas_source_id, selected.tile, selected.alternative));
+ tool_tile_id_label->show();
+ } else {
+ tool_tile_id_label->hide();
+ }
+}
+
+void TileSetAtlasSourceEditor::_update_source_inspector() {
+ // Update the proxy object.
+ atlas_source_proxy_object->edit(tile_set, tile_set_atlas_source, tile_set_atlas_source_id);
+
+ // Update the "clear outside texture" button.
+ tool_advanced_menu_buttom->get_popup()->set_item_disabled(0, !tile_set_atlas_source->has_tiles_outside_texture());
+}
+
+void TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles() {
+ // Fix selected.
+ for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) {
+ TileSelection selected = E->get();
+ if (!tile_set_atlas_source->has_tile(selected.tile) || !tile_set_atlas_source->has_alternative_tile(selected.tile, selected.alternative)) {
+ selection.erase(E);
+ }
+ }
+
+ // Fix hovered.
+ if (!tile_set_atlas_source->has_tile(hovered_base_tile_coords)) {
+ hovered_base_tile_coords = TileSetAtlasSource::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);
+ }
+}
+
+void TileSetAtlasSourceEditor::_update_tile_inspector() {
+ bool has_atlas_tile_selected = (tools_button_group->get_pressed_button() == tool_select_button) && !selection.is_empty();
+
+ // Update the proxy object.
+ if (has_atlas_tile_selected) {
+ tile_proxy_object->edit(tile_set_atlas_source, tile_set_atlas_source_id, selection);
+ }
+
+ // Update visibility.
+ tile_inspector_label->set_visible(has_atlas_tile_selected);
+ tile_inspector->set_visible(has_atlas_tile_selected);
+}
+
+void TileSetAtlasSourceEditor::_update_atlas_view() {
+ // Update the atlas display.
+ tile_atlas_view->set_atlas_source(*tile_set, tile_set_atlas_source, tile_set_atlas_source_id);
+
+ // Create a bunch of buttons to add alternative tiles.
+ for (int i = 0; i < alternative_tiles_control->get_child_count(); i++) {
+ alternative_tiles_control->get_child(i)->queue_delete();
+ }
+
+ Vector2i pos;
+ Vector2 texture_region_base_size = tile_set_atlas_source->get_texture_region_size();
+ int texture_region_base_size_min = MIN(texture_region_base_size.x, texture_region_base_size.y);
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
+ int alternative_count = tile_set_atlas_source->get_alternative_tiles_count(tile_id);
+ if (alternative_count > 1) {
+ // Compute the right extremity of alternative.
+ int y_increment = 0;
+ pos.x = 0;
+ for (int j = 1; j < alternative_count; j++) {
+ int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j);
+ Rect2i rect = tile_atlas_view->get_alternative_tile_rect(tile_id, alternative_id);
+ pos.x = MAX(pos.x, rect.get_end().x);
+ y_increment = MAX(y_increment, rect.size.y);
+ }
+
+ // Create and position the button.
+ Button *button = memnew(Button);
+ alternative_tiles_control->add_child(button);
+ button->set_flat(true);
+ button->set_icon(get_theme_icon("Add", "EditorIcons"));
+ button->add_theme_style_override("normal", memnew(StyleBoxEmpty));
+ button->add_theme_style_override("hover", memnew(StyleBoxEmpty));
+ button->add_theme_style_override("focus", memnew(StyleBoxEmpty));
+ button->add_theme_style_override("pressed", memnew(StyleBoxEmpty));
+ button->connect("pressed", callable_mp(tile_set_atlas_source, &TileSetAtlasSource::create_alternative_tile), varray(tile_id, -1));
+ button->set_rect(Rect2(Vector2(pos.x, pos.y + (y_increment - texture_region_base_size.y) / 2.0), Vector2(texture_region_base_size_min, texture_region_base_size_min)));
+ button->set_expand_icon(true);
+
+ pos.y += y_increment;
+ }
+ }
+ tile_atlas_view->set_padding(Side::SIDE_RIGHT, texture_region_base_size_min);
+
+ // Redraw everything.
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ alternative_tiles_control->update();
+ alternative_tiles_control_unscaled->update();
+ tile_atlas_view->update();
+
+ // Synchronize atlas view.
+ TilesEditor::get_singleton()->synchronize_atlas_view(tile_atlas_view);
+}
+
+void TileSetAtlasSourceEditor::_update_toolbar() {
+ // Hide all settings.
+ for (int i = 0; i < tool_settings->get_child_count(); i++) {
+ Object::cast_to<CanvasItem>(tool_settings->get_child(i))->hide();
+ }
+
+ // SHow only the correct settings.
+ if (tools_button_group->get_pressed_button() == tool_select_button) {
+ } else if (tools_button_group->get_pressed_button() == tool_add_remove_button) {
+ tool_settings_vsep->show();
+ tools_settings_erase_button->show();
+ } else if (tools_button_group->get_pressed_button() == tool_add_remove_rect_button) {
+ tool_settings_vsep->show();
+ tools_settings_erase_button->show();
+ }
+}
+
+void TileSetAtlasSourceEditor::_tile_atlas_control_mouse_exited() {
+ hovered_base_tile_coords = TileSetAtlasSource::INVALID_ATLAS_COORDS;
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ tile_atlas_view->update();
+}
+
+void TileSetAtlasSourceEditor::_tile_atlas_view_transform_changed() {
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+}
+
+void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEvent> &p_event) {
+ // Update the hovered coords.
+ hovered_base_tile_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
+
+ // Handle the event.
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos);
+ Vector2i last_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_mouse_pos);
+ Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
+
+ Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
+
+ if (drag_type == DRAG_TYPE_NONE) {
+ 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) {
+ 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);
+ Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom();
+ Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0);
+ const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) };
+ const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) };
+ CursorShape cursor_shape = CURSOR_ARROW;
+ bool can_grow[4];
+ for (int i = 0; i < 4; i++) {
+ can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]);
+ can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1;
+ }
+ for (int i = 0; i < 4; i++) {
+ Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i];
+ if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) {
+ cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE;
+ }
+ Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4];
+ if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) {
+ cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE;
+ }
+ }
+ tile_atlas_control->set_default_cursor_shape(cursor_shape);
+ }
+ }
+ } else if (drag_type == DRAG_TYPE_CREATE_BIG_TILE) {
+ // Create big tile.
+ new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
+
+ Rect2i new_rect = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
+ new_rect.size += Vector2i(1, 1);
+ // Check if the new tile can fit in the new rect.
+ if (tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size)) {
+ // Move and resize the tile.
+ tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size);
+ drag_current_tile = new_rect.position;
+ }
+ } else if (drag_type == DRAG_TYPE_CREATE_TILES) {
+ // Create tiles.
+ last_base_tiles_coords = last_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
+ new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
+
+ 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) {
+ tile_set_atlas_source->create_tile(line[i]);
+ drag_modified_tiles.insert(line[i]);
+ }
+ }
+
+ drag_last_mouse_pos = tile_atlas_control->get_local_mouse_position();
+
+ } else if (drag_type == DRAG_TYPE_REMOVE_TILES) {
+ // Remove tiles.
+ last_base_tiles_coords = last_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
+ new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
+
+ 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) {
+ drag_modified_tiles.insert(base_tile_coords);
+ }
+ }
+
+ drag_last_mouse_pos = tile_atlas_control->get_local_mouse_position();
+ } else if (drag_type == DRAG_TYPE_MOVE_TILE) {
+ // Move tile.
+ Vector2 mouse_offset = (Vector2(tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile)) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size();
+ Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position() - mouse_offset);
+ coords = coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
+ if (drag_current_tile != coords && tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, coords)) {
+ tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, coords);
+ selection.clear();
+ selection.insert({ coords, 0 });
+ drag_current_tile = coords;
+
+ // Update only what's needed.
+ tile_set_atlas_source_changed_needs_update = false;
+ _update_tile_inspector();
+ _update_atlas_view();
+ _update_tile_id_label();
+ }
+ } else if (drag_type >= DRAG_TYPE_RESIZE_TOP_LEFT && drag_type <= DRAG_TYPE_RESIZE_LEFT) {
+ // Resizing a tile.
+ new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(-1, -1)).min(grid_size);
+
+ Rect2i old_rect = Rect2i(drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile));
+ Rect2i new_rect = old_rect;
+
+ if (drag_type == DRAG_TYPE_RESIZE_LEFT || drag_type == DRAG_TYPE_RESIZE_TOP_LEFT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_LEFT) {
+ new_rect.position.x = MIN(new_base_tiles_coords.x + 1, old_rect.get_end().x - 1);
+ new_rect.size.x = old_rect.get_end().x - new_rect.position.x;
+ }
+ if (drag_type == DRAG_TYPE_RESIZE_TOP || drag_type == DRAG_TYPE_RESIZE_TOP_LEFT || drag_type == DRAG_TYPE_RESIZE_TOP_RIGHT) {
+ new_rect.position.y = MIN(new_base_tiles_coords.y + 1, old_rect.get_end().y - 1);
+ new_rect.size.y = old_rect.get_end().y - new_rect.position.y;
+ }
+
+ if (drag_type == DRAG_TYPE_RESIZE_RIGHT || drag_type == DRAG_TYPE_RESIZE_TOP_RIGHT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_RIGHT) {
+ new_rect.set_end(Vector2i(MAX(new_base_tiles_coords.x, old_rect.position.x + 1), new_rect.get_end().y));
+ }
+ if (drag_type == DRAG_TYPE_RESIZE_BOTTOM || drag_type == DRAG_TYPE_RESIZE_BOTTOM_LEFT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_RIGHT) {
+ new_rect.set_end(Vector2i(new_rect.get_end().x, MAX(new_base_tiles_coords.y, old_rect.position.y + 1)));
+ }
+
+ if (tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size)) {
+ tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size);
+ selection.clear();
+ selection.insert({ new_rect.position, 0 });
+ drag_current_tile = new_rect.position;
+
+ // Update only what's needed.
+ tile_set_atlas_source_changed_needs_update = false;
+ _update_tile_inspector();
+ _update_atlas_view();
+ _update_tile_id_label();
+ }
+ }
+
+ // Redraw for the hovered tile.
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ alternative_tiles_control->update();
+ alternative_tiles_control_unscaled->update();
+ tile_atlas_view->update();
+ return;
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
+ Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position();
+ if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed()) {
+ // Left click pressed.
+ if (tools_button_group->get_pressed_button() == tool_add_remove_button) {
+ if (tools_settings_erase_button->is_pressed()) {
+ // Remove tiles.
+
+ // Setup the dragging info.
+ drag_type = DRAG_TYPE_REMOVE_TILES;
+ drag_start_mouse_pos = mouse_local_pos;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+
+ // 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) {
+ coords = tile_set_atlas_source->get_tile_at_coords(coords);
+ }
+ if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+ drag_modified_tiles.insert(coords);
+ }
+ } else {
+ if (mb->get_shift()) {
+ // 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) {
+ // 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;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+ drag_current_tile = coords;
+
+ // Create a tile.
+ tile_set_atlas_source->create_tile(coords);
+ }
+ } else {
+ // Create tiles.
+
+ // Setup the dragging info.
+ drag_type = DRAG_TYPE_CREATE_TILES;
+ drag_start_mouse_pos = mouse_local_pos;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+
+ // 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) {
+ tile_set_atlas_source->create_tile(coords);
+ drag_modified_tiles.insert(coords);
+ }
+ }
+ }
+ } else if (tools_button_group->get_pressed_button() == tool_add_remove_rect_button) {
+ if (tools_settings_erase_button->is_pressed()) {
+ // Remove tiles using rect.
+
+ // Setup the dragging info.
+ drag_type = DRAG_TYPE_REMOVE_TILES_USING_RECT;
+ drag_start_mouse_pos = mouse_local_pos;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+ } else {
+ if (mb->get_shift()) {
+ // 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) {
+ // 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;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+ drag_current_tile = coords;
+
+ // Create a tile.
+ tile_set_atlas_source->create_tile(coords);
+ }
+ } else {
+ // Create tiles using rect.
+ drag_type = DRAG_TYPE_CREATE_TILES_USING_RECT;
+ drag_start_mouse_pos = mouse_local_pos;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+ }
+ }
+ } else if (tools_button_group->get_pressed_button() == tool_select_button) {
+ // Dragging a handle.
+ drag_type = DRAG_TYPE_NONE;
+ if (selection.size() == 1) {
+ TileSelection selected = selection.front()->get();
+ if (selected.tile != TileSetAtlasSource::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();
+ Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0);
+ const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) };
+ const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) };
+ CursorShape cursor_shape = CURSOR_ARROW;
+ bool can_grow[4];
+ for (int i = 0; i < 4; i++) {
+ can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]);
+ can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1;
+ }
+ for (int i = 0; i < 4; i++) {
+ Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i];
+ if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) {
+ drag_type = (DragType)((int)DRAG_TYPE_RESIZE_TOP_LEFT + i * 2);
+ drag_start_mouse_pos = mouse_local_pos;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+ drag_current_tile = selected.tile;
+ drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile));
+ cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE;
+ }
+ Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4];
+ if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) {
+ drag_type = (DragType)((int)DRAG_TYPE_RESIZE_TOP + i * 2);
+ drag_start_mouse_pos = mouse_local_pos;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+ drag_current_tile = selected.tile;
+ drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile));
+ cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE;
+ }
+ }
+ tile_atlas_control->set_default_cursor_shape(cursor_shape);
+ }
+ }
+
+ // Selecting then dragging a tile.
+ if (drag_type == DRAG_TYPE_NONE) {
+ TileSelection selected = { TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE };
+ Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos);
+ if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+ coords = tile_set_atlas_source->get_tile_at_coords(coords);
+ if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+ selected = { coords, 0 };
+ }
+ }
+
+ bool shift = mb->get_shift();
+ if (!shift && selection.size() == 1 && selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS && selection.has(selected)) {
+ // Start move dragging.
+ drag_type = DRAG_TYPE_MOVE_TILE;
+ drag_start_mouse_pos = mouse_local_pos;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+ drag_current_tile = selected.tile;
+ drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile));
+ tile_atlas_control->set_default_cursor_shape(CURSOR_MOVE);
+ } else {
+ // Start selection dragging.
+ drag_type = DRAG_TYPE_RECT_SELECT;
+ drag_start_mouse_pos = mouse_local_pos;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+ }
+ }
+ }
+ } else {
+ // Left click released.
+ _end_dragging();
+ }
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ alternative_tiles_control->update();
+ alternative_tiles_control_unscaled->update();
+ tile_atlas_view->update();
+ return;
+ } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
+ if (mb->is_pressed()) {
+ // 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) {
+ 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) {
+ undo_redo->create_action(TTR("Select tiles"));
+ undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array());
+ selection.clear();
+ selection.insert(selected);
+ undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array());
+ undo_redo->commit_action(false);
+ _update_tile_inspector();
+ _update_tile_id_label();
+ }
+ }
+
+ // Pops up the correct menu, depending on whether we have a tile or not.
+ if (selected.tile != TileSetAtlasSource::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) {
+ // We don't have a tile, but can create one.
+ menu_option_coords = hovered_base_tile_coords;
+ menu_option_alternative = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
+ empty_base_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i()));
+ }
+ } else {
+ // Right click released.
+ _end_dragging();
+ }
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ alternative_tiles_control->update();
+ alternative_tiles_control_unscaled->update();
+ tile_atlas_view->update();
+ return;
+ }
+ }
+}
+
+void TileSetAtlasSourceEditor::_end_dragging() {
+ switch (drag_type) {
+ case DRAG_TYPE_CREATE_TILES:
+ undo_redo->create_action(TTR("Create tiles"));
+ for (Set<Vector2i>::Element *E = drag_modified_tiles.front(); E; E = E->next()) {
+ undo_redo->add_do_method(tile_set_atlas_source, "create_tile", E->get());
+ undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", E->get());
+ }
+ undo_redo->commit_action(false);
+ break;
+ case DRAG_TYPE_CREATE_BIG_TILE:
+ undo_redo->create_action(TTR("Create a tile"));
+ undo_redo->add_do_method(tile_set_atlas_source, "create_tile", drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile));
+ undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", drag_current_tile);
+ undo_redo->commit_action(false);
+ break;
+ case DRAG_TYPE_REMOVE_TILES: {
+ List<PropertyInfo> list;
+ tile_set_atlas_source->get_property_list(&list);
+ Map<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source);
+ undo_redo->create_action(TTR("Remove tiles"));
+ for (Set<Vector2i>::Element *E = drag_modified_tiles.front(); E; E = E->next()) {
+ Vector2i coords = E->get();
+ undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", coords);
+ undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", coords);
+ if (per_tile.has(coords)) {
+ for (List<const PropertyInfo *>::Element *E_property = per_tile[coords].front(); E_property; E_property = E_property->next()) {
+ String property = E_property->get()->name;
+ Variant value = tile_set_atlas_source->get(property);
+ if (value.get_type() != Variant::NIL) {
+ undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value);
+ }
+ }
+ }
+ }
+ undo_redo->commit_action();
+ } break;
+ case DRAG_TYPE_CREATE_TILES_USING_RECT: {
+ 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());
+ Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
+ area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size()));
+ undo_redo->create_action(TTR("Create tiles"));
+ 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) {
+ undo_redo->add_do_method(tile_set_atlas_source, "create_tile", coords);
+ undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", coords);
+ }
+ }
+ }
+ undo_redo->commit_action();
+ } break;
+ case DRAG_TYPE_REMOVE_TILES_USING_RECT: {
+ 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());
+ Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
+ area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size()));
+ List<PropertyInfo> list;
+ tile_set_atlas_source->get_property_list(&list);
+ Map<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source);
+
+ Set<Vector2i> to_delete;
+ 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) {
+ to_delete.insert(coords);
+ }
+ }
+ }
+
+ undo_redo->create_action(TTR("Remove tiles"));
+ undo_redo->add_do_method(this, "_set_selection_from_array", Array());
+ for (Set<Vector2i>::Element *E = to_delete.front(); E; E = E->next()) {
+ Vector2i coords = E->get();
+ undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", coords);
+ undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", coords);
+ if (per_tile.has(coords)) {
+ for (List<const PropertyInfo *>::Element *E_property = per_tile[coords].front(); E_property; E_property = E_property->next()) {
+ String property = E_property->get()->name;
+ Variant value = tile_set_atlas_source->get(property);
+ if (value.get_type() != Variant::NIL) {
+ undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value);
+ }
+ }
+ }
+ }
+ undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array());
+ undo_redo->commit_action();
+ } break;
+ case DRAG_TYPE_MOVE_TILE:
+ if (drag_current_tile != drag_start_tile_shape.position) {
+ undo_redo->create_action(TTR("Move a tile"));
+ undo_redo->add_do_method(tile_set_atlas_source, "move_tile_in_atlas", drag_start_tile_shape.position, drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile));
+ undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array());
+ undo_redo->add_undo_method(tile_set_atlas_source, "move_tile_in_atlas", drag_current_tile, drag_start_tile_shape.position, drag_start_tile_shape.size);
+ Array array;
+ array.push_back(drag_start_tile_shape.position);
+ array.push_back(0);
+ undo_redo->add_undo_method(this, "_set_selection_from_array", array);
+ undo_redo->commit_action(false);
+ }
+ break;
+ 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);
+
+ Rect2i region = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
+ region.size += Vector2i(1, 1);
+
+ undo_redo->create_action(TTR("Select tiles"));
+ undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array());
+
+ // Determine if we clear, then add or remove to the selection.
+ 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 (selection.has({ coords, 0 })) {
+ add_to_selection = false;
+ }
+ }
+ } else {
+ selection.clear();
+ }
+
+ // Modify 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 coords = Vector2i(x, y);
+ coords = tile_set_atlas_source->get_tile_at_coords(coords);
+ if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+ if (add_to_selection && !selection.has({ coords, 0 })) {
+ selection.insert({ coords, 0 });
+ } else if (!add_to_selection && selection.has({ coords, 0 })) {
+ selection.erase({ coords, 0 });
+ }
+ }
+ }
+ }
+ _update_tile_inspector();
+ _update_tile_id_label();
+ undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array());
+ undo_redo->commit_action(false);
+ break;
+ }
+ case DRAG_TYPE_RESIZE_TOP_LEFT:
+ case DRAG_TYPE_RESIZE_TOP:
+ case DRAG_TYPE_RESIZE_TOP_RIGHT:
+ case DRAG_TYPE_RESIZE_RIGHT:
+ case DRAG_TYPE_RESIZE_BOTTOM_RIGHT:
+ case DRAG_TYPE_RESIZE_BOTTOM:
+ case DRAG_TYPE_RESIZE_BOTTOM_LEFT:
+ case DRAG_TYPE_RESIZE_LEFT:
+ if (drag_start_tile_shape != Rect2i(drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile))) {
+ undo_redo->create_action(TTR("Resize a tile"));
+ undo_redo->add_do_method(tile_set_atlas_source, "move_tile_in_atlas", drag_start_tile_shape.position, drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile));
+ undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array());
+ undo_redo->add_undo_method(tile_set_atlas_source, "move_tile_in_atlas", drag_current_tile, drag_start_tile_shape.position, drag_start_tile_shape.size);
+ Array array;
+ array.push_back(drag_start_tile_shape.position);
+ array.push_back(0);
+ undo_redo->add_undo_method(this, "_set_selection_from_array", array);
+ undo_redo->commit_action(false);
+ }
+ break;
+ default:
+ break;
+ }
+
+ drag_modified_tiles.clear();
+ drag_type = DRAG_TYPE_NONE;
+ tile_atlas_control->set_default_cursor_shape(CURSOR_ARROW);
+}
+
+Map<Vector2i, List<const PropertyInfo *>> TileSetAtlasSourceEditor::_group_properties_per_tiles(const List<PropertyInfo> &r_list, const TileSetAtlasSource *p_atlas) {
+ // Group properties per tile.
+ Map<Vector2i, List<const PropertyInfo *>> per_tile;
+ for (const List<PropertyInfo>::Element *E_property = r_list.front(); E_property; E_property = E_property->next()) {
+ Vector<String> components = String(E_property->get().name).split("/", true, 1);
+ if (components.size() >= 1) {
+ Vector<String> coord_arr = components[0].split(":");
+ if (coord_arr.size() == 2 && coord_arr[0].is_valid_integer() && coord_arr[1].is_valid_integer()) {
+ Vector2i coords = Vector2i(coord_arr[0].to_int(), coord_arr[1].to_int());
+ per_tile[coords].push_back(&(E_property->get()));
+ }
+ }
+ }
+ return per_tile;
+}
+
+void TileSetAtlasSourceEditor::_menu_option(int p_option) {
+ switch (p_option) {
+ case TILE_DELETE: {
+ List<PropertyInfo> list;
+ tile_set_atlas_source->get_property_list(&list);
+ Map<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source);
+ undo_redo->create_action(TTR("Remove tile"));
+
+ // Remove tiles
+ Set<Vector2i> removed;
+ for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) {
+ TileSelection selected = E->get();
+ if (selected.alternative == 0) {
+ // Remove a tile.
+ undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", selected.tile);
+ undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", selected.tile);
+ removed.insert(selected.tile);
+ if (per_tile.has(selected.tile)) {
+ for (List<const PropertyInfo *>::Element *E_property = per_tile[selected.tile].front(); E_property; E_property = E_property->next()) {
+ String property = E_property->get()->name;
+ Variant value = tile_set_atlas_source->get(property);
+ if (value.get_type() != Variant::NIL) {
+ undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value);
+ }
+ }
+ }
+ }
+ }
+
+ // Remove alternatives
+ for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) {
+ TileSelection selected = E->get();
+ if (selected.alternative > 0 && !removed.has(selected.tile)) {
+ // Remove an alternative tile.
+ undo_redo->add_do_method(tile_set_atlas_source, "remove_alternative_tile", selected.tile, selected.alternative);
+ undo_redo->add_undo_method(tile_set_atlas_source, "create_alternative_tile", selected.tile, selected.alternative);
+ if (per_tile.has(selected.tile)) {
+ for (List<const PropertyInfo *>::Element *E_property = per_tile[selected.tile].front(); E_property; E_property = E_property->next()) {
+ Vector<String> components = E_property->get()->name.split("/", true, 2);
+ if (components.size() >= 2 && components[1].is_valid_integer() && components[1].to_int() == selected.alternative) {
+ String property = E_property->get()->name;
+ Variant value = tile_set_atlas_source->get(property);
+ if (value.get_type() != Variant::NIL) {
+ undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value);
+ }
+ }
+ }
+ }
+ }
+ }
+ undo_redo->commit_action();
+ _update_fix_selected_and_hovered_tiles();
+ _update_tile_id_label();
+ } break;
+ case TILE_CREATE: {
+ undo_redo->create_action(TTR("Create a tile"));
+ undo_redo->add_do_method(tile_set_atlas_source, "create_tile", menu_option_coords);
+ Array array;
+ array.push_back(menu_option_coords);
+ array.push_back(0);
+ undo_redo->add_do_method(this, "_set_selection_from_array", array);
+ undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", menu_option_coords);
+ undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array());
+ undo_redo->commit_action();
+ _update_tile_id_label();
+ } break;
+ case TILE_CREATE_ALTERNATIVE: {
+ undo_redo->create_action(TTR("Create tile alternatives"));
+ Array array;
+ for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) {
+ if (E->get().alternative == 0) {
+ int next_id = tile_set_atlas_source->get_next_alternative_tile_id(E->get().tile);
+ undo_redo->add_do_method(tile_set_atlas_source, "create_alternative_tile", E->get().tile, next_id);
+ array.push_back(E->get().tile);
+ array.push_back(next_id);
+ undo_redo->add_undo_method(tile_set_atlas_source, "remove_alternative_tile", E->get().tile, next_id);
+ }
+ }
+ undo_redo->add_do_method(this, "_set_selection_from_array", array);
+ undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array());
+ undo_redo->commit_action();
+ _update_tile_id_label();
+ } break;
+ case ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE: {
+ tile_set_atlas_source->clear_tiles_outside_texture();
+ } break;
+ case ADVANCED_AUTO_CREATE_TILES: {
+ _auto_create_tiles();
+ } break;
+ case ADVANCED_AUTO_REMOVE_TILES: {
+ _auto_remove_tiles();
+ } break;
+ }
+}
+
+void TileSetAtlasSourceEditor::_unhandled_key_input(const Ref<InputEvent> &p_event) {
+ // Check for shortcuts.
+ if (ED_IS_SHORTCUT("tiles_editor/delete_tile", p_event)) {
+ if (tools_button_group->get_pressed_button() == tool_select_button && !selection.is_empty()) {
+ _menu_option(TILE_DELETE);
+ accept_event();
+ }
+ }
+}
+
+void TileSetAtlasSourceEditor::_set_selection_from_array(Array p_selection) {
+ ERR_FAIL_COND((p_selection.size() % 2) != 0);
+ selection.clear();
+ for (int i = 0; i < p_selection.size() / 2; i++) {
+ TileSelection selected = { p_selection[i * 2], p_selection[i * 2 + 1] };
+ if (tile_set_atlas_source->has_tile(selected.tile) && tile_set_atlas_source->has_alternative_tile(selected.tile, selected.alternative)) {
+ selection.insert(selected);
+ }
+ }
+ _update_tile_inspector();
+ _update_tile_id_label();
+ _update_atlas_view();
+}
+
+Array TileSetAtlasSourceEditor::_get_selection_as_array() {
+ Array output;
+ for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) {
+ output.push_back(E->get().tile);
+ output.push_back(E->get().alternative);
+ }
+ return output;
+}
+
+void TileSetAtlasSourceEditor::_tile_atlas_control_draw() {
+ // Draw the selected tile.
+ if (tools_button_group->get_pressed_button() == tool_select_button) {
+ for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) {
+ TileSelection selected = E->get();
+ if (selected.alternative == 0) {
+ // Draw the rect.
+ Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile);
+ tile_atlas_control->draw_rect(region, Color(0.2, 0.2, 1.0), false);
+ }
+ }
+
+ if (selection.size() == 1) {
+ // Draw the resize handles (only when it's possible to expand).
+ TileSelection selected = selection.front()->get();
+ Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile);
+ Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom();
+ Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile);
+ Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0);
+ Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) };
+ Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) };
+ bool can_grow[4];
+ for (int i = 0; i < 4; i++) {
+ can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]);
+ can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1;
+ }
+ for (int i = 0; i < 4; i++) {
+ Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i];
+ if (can_grow[i] && can_grow[(i + 3) % 4]) {
+ tile_atlas_control->draw_texture_rect(resize_handle, Rect2(pos, zoomed_size), false);
+ } else {
+ tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2(pos, zoomed_size), false);
+ }
+ Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4];
+ if (can_grow[i]) {
+ tile_atlas_control->draw_texture_rect(resize_handle, Rect2((pos + next_pos) / 2.0, zoomed_size), false);
+ } else {
+ tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2((pos + next_pos) / 2.0, zoomed_size), false);
+ }
+ }
+ }
+ }
+
+ if (drag_type == DRAG_TYPE_REMOVE_TILES) {
+ // Draw the tiles to be removed.
+ for (Set<Vector2i>::Element *E = drag_modified_tiles.front(); E; E = E->next()) {
+ tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(E->get()), Color(0.0, 0.0, 0.0), false);
+ }
+ } else if (drag_type == DRAG_TYPE_RECT_SELECT || drag_type == DRAG_TYPE_REMOVE_TILES_USING_RECT) {
+ // Draw tiles to be removed.
+ 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());
+ Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
+ area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size()));
+
+ Color color = Color(0.0, 0.0, 0.0);
+ if (drag_type == DRAG_TYPE_RECT_SELECT) {
+ color = Color(0.5, 0.5, 1.0);
+ }
+
+ Set<Vector2i> to_paint;
+ 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) {
+ to_paint.insert(coords);
+ }
+ }
+ }
+
+ for (Set<Vector2i>::Element *E = to_paint.front(); E; E = E->next()) {
+ Vector2i coords = E->get();
+ tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(coords), color, false);
+ }
+ } else if (drag_type == DRAG_TYPE_CREATE_TILES_USING_RECT) {
+ // Draw tiles to be created.
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i separation = tile_set_atlas_source->get_separation();
+ Vector2i tile_size = tile_set_atlas_source->get_texture_region_size();
+
+ 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());
+ Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
+ area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size()));
+ 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) {
+ Vector2i origin = margins + (coords * (tile_size + separation));
+ tile_atlas_control->draw_rect(Rect2i(origin, tile_size), Color(1.0, 1.0, 1.0), false);
+ }
+ }
+ }
+ }
+
+ // Draw the hovered tile.
+ if (drag_type == DRAG_TYPE_REMOVE_TILES_USING_RECT || drag_type == DRAG_TYPE_CREATE_TILES_USING_RECT) {
+ // Draw the rect.
+ 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());
+ Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
+ area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size()));
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i separation = tile_set_atlas_source->get_separation();
+ Vector2i tile_size = tile_set_atlas_source->get_texture_region_size();
+ Vector2i origin = margins + (area.position * (tile_size + separation));
+ tile_atlas_control->draw_rect(Rect2i(origin, area.size * tile_size), Color(1.0, 1.0, 1.0), false);
+ } else {
+ 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) {
+ // 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 {
+ // Draw empty tile, only in add/remove tiles mode.
+ if (tools_button_group->get_pressed_button() == tool_add_remove_button || tools_button_group->get_pressed_button() == tool_add_remove_rect_button) {
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i separation = tile_set_atlas_source->get_separation();
+ Vector2i tile_size = tile_set_atlas_source->get_texture_region_size();
+ Vector2i origin = margins + (hovered_base_tile_coords * (tile_size + separation));
+ tile_atlas_control->draw_rect(Rect2i(origin, tile_size), Color(1.0, 1.0, 1.0), false);
+ }
+ }
+ }
+ }
+}
+
+void TileSetAtlasSourceEditor::_tile_atlas_control_unscaled_draw() {
+ // Draw the preview of the selected property.
+ TileDataEditor *tile_data_editor = TileSetEditor::get_singleton()->get_tile_data_editor(selected_property);
+ if (tile_data_editor && tile_inspector->is_visible_in_tree()) {
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i coords = tile_set_atlas_source->get_tile_id(i);
+ Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(coords);
+ Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0);
+
+ Transform2D xform = tile_atlas_control->get_parent_control()->get_transform();
+ xform.translate(position);
+
+ tile_data_editor->draw_over_tile(tile_atlas_control_unscaled, xform, *tile_set, tile_set_atlas_source_id, coords, 0, selected_property);
+ }
+ }
+}
+
+void TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event) {
+ // Update the hovered alternative tile.
+ hovered_alternative_tile_coords = tile_atlas_view->get_alternative_tile_at_pos(alternative_tiles_control->get_local_mouse_position());
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ alternative_tiles_control->update();
+ alternative_tiles_control_unscaled->update();
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
+ drag_type = DRAG_TYPE_NONE;
+
+ Vector2 mouse_local_pos = alternative_tiles_control->get_local_mouse_position();
+ if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed()) {
+ // Left click pressed.
+ if (tools_button_group->get_pressed_button() == tool_select_button) {
+ Vector3 tile = tile_atlas_view->get_alternative_tile_at_pos(mouse_local_pos);
+
+ selection.clear();
+ TileSelection selected = { Vector2i(tile.x, tile.y), int(tile.z) };
+ if (selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+ selection.insert(selected);
+ }
+
+ _update_tile_inspector();
+ _update_tile_id_label();
+ }
+ }
+ } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
+ if (mb->is_pressed()) {
+ // Right click pressed
+ Vector3 tile = tile_atlas_view->get_alternative_tile_at_pos(mouse_local_pos);
+
+ selection.clear();
+ TileSelection selected = { Vector2i(tile.x, tile.y), int(tile.z) };
+ if (selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+ selection.insert(selected);
+ }
+
+ _update_tile_inspector();
+ _update_tile_id_label();
+
+ if (selection.size() == 1) {
+ selected = selection.front()->get();
+ menu_option_coords = selected.tile;
+ menu_option_alternative = selected.alternative;
+ alternative_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i()));
+ }
+ }
+ }
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ alternative_tiles_control->update();
+ alternative_tiles_control_unscaled->update();
+ }
+}
+
+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);
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ alternative_tiles_control->update();
+ alternative_tiles_control_unscaled->update();
+}
+
+void TileSetAtlasSourceEditor::_tile_alternatives_control_draw() {
+ // Update the hovered alternative tile.
+ 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) {
+ 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);
+ }
+ }
+
+ // Draw selected tile.
+ for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) {
+ TileSelection selected = E->get();
+ if (selected.alternative >= 1) {
+ Rect2i rect = tile_atlas_view->get_alternative_tile_rect(selected.tile, selected.alternative);
+ if (rect != Rect2i()) {
+ alternative_tiles_control->draw_rect(rect, Color(0.2, 0.2, 1.0), false);
+ }
+ }
+ }
+ }
+}
+
+void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() {
+ //TODO
+}
+
+void TileSetAtlasSourceEditor::_tile_set_atlas_source_changed() {
+ tile_set_atlas_source_changed_needs_update = true;
+}
+
+void TileSetAtlasSourceEditor::_atlas_source_proxy_object_changed(String p_what) {
+ if (p_what == "texture" && !atlas_source_proxy_object->get("texture").is_null()) {
+ confirm_auto_create_tiles->popup_centered();
+ } else if (p_what == "id") {
+ emit_signal("source_id_changed", atlas_source_proxy_object->get_id());
+ }
+}
+
+void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) {
+ UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo);
+ ERR_FAIL_COND(!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);
+ if (tile_data) {
+ Vector<String> components = String(p_property).split("/", true, 2);
+ if (components.size() == 2 && components[1] == "shapes_count") {
+ int layer_index = components[0].trim_prefix("physics_layer_").to_int();
+ int new_shapes_count = p_new_value;
+ int old_shapes_count = tile_data->get(vformat("physics_layer_%d/shapes_count", layer_index));
+ if (new_shapes_count < old_shapes_count) {
+ for (int i = new_shapes_count - 1; i < old_shapes_count; i++) {
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/shape", layer_index, i));
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/one_way", layer_index, i));
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/one_way_margin", layer_index, i));
+ }
+ }
+ }
+ }
+#undef ADD_UNDO
+}
+
+void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id) {
+ ERR_FAIL_COND(!p_tile_set.is_valid());
+ ERR_FAIL_COND(!p_tile_set_atlas_source);
+ ERR_FAIL_COND(p_source_id < 0);
+ ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source);
+
+ if (p_tile_set == tile_set && p_tile_set_atlas_source == tile_set_atlas_source && p_source_id == tile_set_atlas_source_id) {
+ return;
+ }
+
+ // Remove listener for old objects.
+ if (tile_set_atlas_source) {
+ tile_set_atlas_source->disconnect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_atlas_source_changed));
+ }
+
+ // Clear the selection.
+ selection.clear();
+
+ // Change the edited object.
+ tile_set = p_tile_set;
+ tile_set_atlas_source = p_tile_set_atlas_source;
+ tile_set_atlas_source_id = p_source_id;
+
+ // Add the listener again.
+ if (tile_set_atlas_source) {
+ tile_set_atlas_source->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_atlas_source_changed));
+ }
+
+ // Update everything.
+ _update_source_inspector();
+
+ // Update the selected tile.
+ _update_fix_selected_and_hovered_tiles();
+ _update_tile_id_label();
+ _update_atlas_view();
+ _update_tile_inspector();
+}
+
+void TileSetAtlasSourceEditor::init_source() {
+ confirm_auto_create_tiles->popup_centered();
+}
+
+void TileSetAtlasSourceEditor::_auto_create_tiles() {
+ if (!tile_set_atlas_source) {
+ return;
+ }
+
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+ if (texture.is_valid()) {
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i separation = tile_set_atlas_source->get_separation();
+ Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
+ Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
+ undo_redo->create_action(TTR("Create tiles in non-transparent texture regions"));
+ for (int y = 0; y < grid_size.y; y++) {
+ 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) {
+ // 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;
+ for (int region_x = region.get_position().x; region_x < region.get_end().x; region_x++) {
+ for (int region_y = region.get_position().y; region_y < region.get_end().y; region_y++) {
+ if (texture->is_pixel_opaque(region_x, region_y)) {
+ is_opaque = true;
+ break;
+ }
+ }
+ if (is_opaque) {
+ break;
+ }
+ }
+
+ // If we do have opaque pixels, create a tile.
+ if (is_opaque) {
+ undo_redo->add_do_method(tile_set_atlas_source, "create_tile", coords);
+ undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", coords);
+ }
+ }
+ }
+ }
+ undo_redo->commit_action();
+ }
+}
+
+void TileSetAtlasSourceEditor::_auto_remove_tiles() {
+ if (!tile_set_atlas_source) {
+ return;
+ }
+
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+ if (texture.is_valid()) {
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i separation = tile_set_atlas_source->get_separation();
+ Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
+ Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
+
+ undo_redo->create_action(TTR("Remove tiles in fully transparent texture regions"));
+
+ List<PropertyInfo> list;
+ tile_set_atlas_source->get_property_list(&list);
+ Map<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source);
+
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i coords = tile_set_atlas_source->get_tile_id(i);
+ Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(coords);
+
+ // Skip tiles outside texture.
+ if ((coords.x + size_in_atlas.x) > grid_size.x || (coords.y + size_in_atlas.y) > grid_size.y) {
+ continue;
+ }
+
+ // Check if the texture is empty at the given coords.
+ Rect2i region = Rect2i(margins + (coords * (texture_region_size + separation)), texture_region_size * size_in_atlas);
+ bool is_opaque = false;
+ for (int region_x = region.get_position().x; region_x < region.get_end().x; region_x++) {
+ for (int region_y = region.get_position().y; region_y < region.get_end().y; region_y++) {
+ if (texture->is_pixel_opaque(region_x, region_y)) {
+ is_opaque = true;
+ break;
+ }
+ }
+ if (is_opaque) {
+ break;
+ }
+ }
+
+ // If we do have opaque pixels, create a tile.
+ if (!is_opaque) {
+ undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", coords);
+ undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", coords);
+ if (per_tile.has(coords)) {
+ for (List<const PropertyInfo *>::Element *E_property = per_tile[coords].front(); E_property; E_property = E_property->next()) {
+ String property = E_property->get()->name;
+ Variant value = tile_set_atlas_source->get(property);
+ if (value.get_type() != Variant::NIL) {
+ undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value);
+ }
+ }
+ }
+ }
+ }
+ undo_redo->commit_action();
+ }
+}
+
+void TileSetAtlasSourceEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED:
+ tool_select_button->set_icon(get_theme_icon("ToolSelect", "EditorIcons"));
+ tool_add_remove_button->set_icon(get_theme_icon("EditAddRemove", "EditorIcons"));
+ tool_add_remove_rect_button->set_icon(get_theme_icon("RectangleAddRemove", "EditorIcons"));
+
+ tools_settings_erase_button->set_icon(get_theme_icon("Eraser", "EditorIcons"));
+
+ tool_advanced_menu_buttom->set_icon(get_theme_icon("Tools", "EditorIcons"));
+
+ resize_handle = get_theme_icon("EditorHandle", "EditorIcons");
+ resize_handle_disabled = get_theme_icon("EditorHandleDisabled", "EditorIcons");
+ break;
+ case NOTIFICATION_INTERNAL_PROCESS:
+ if (tile_set_atlas_source_changed_needs_update) {
+ // Update everything.
+ _update_source_inspector();
+
+ // Update the selected tile.
+ _update_fix_selected_and_hovered_tiles();
+ _update_tile_id_label();
+ _update_atlas_view();
+ _update_tile_inspector();
+
+ tile_set_atlas_source_changed_needs_update = false;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void TileSetAtlasSourceEditor::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &TileSetAtlasSourceEditor::_unhandled_key_input);
+ ClassDB::bind_method(D_METHOD("_set_selection_from_array"), &TileSetAtlasSourceEditor::_set_selection_from_array);
+
+ ADD_SIGNAL(MethodInfo("source_id_changed", PropertyInfo(Variant::INT, "source_id")));
+}
+
+TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() {
+ set_process_unhandled_key_input(true);
+ set_process_internal(true);
+
+ // -- 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);
+
+ // 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(TileProxyObject(this));
+ tile_proxy_object->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view).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);
+ tile_inspector->connect("property_selected", callable_mp(this, &TileSetAtlasSourceEditor::_inspector_property_selected));
+ middle_vbox_container->add_child(tile_inspector);
+
+ // Atlas source inspector.
+ atlas_source_inspector_label = memnew(Label);
+ atlas_source_inspector_label->set_text(TTR("Atlas Properties:"));
+ middle_vbox_container->add_child(atlas_source_inspector_label);
+
+ atlas_source_proxy_object = memnew(TileSetAtlasSourceProxyObject());
+ atlas_source_proxy_object->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_atlas_source_proxy_object_changed));
+
+ atlas_source_inspector = memnew(EditorInspector);
+ atlas_source_inspector->set_undo_redo(undo_redo);
+ atlas_source_inspector->set_enable_v_scroll(false);
+ atlas_source_inspector->edit(atlas_source_proxy_object);
+ middle_vbox_container->add_child(atlas_source_inspector);
+
+ // Right panel.
+ VBoxContainer *right_panel = memnew(VBoxContainer);
+ right_panel->set_h_size_flags(SIZE_EXPAND_FILL);
+ right_panel->set_v_size_flags(SIZE_EXPAND_FILL);
+ split_container_right_side->add_child(right_panel);
+
+ // -- Dialogs --
+ confirm_auto_create_tiles = memnew(AcceptDialog);
+ confirm_auto_create_tiles->set_title(TTR("Create tiles automatically in non-transparent texture regions?"));
+ confirm_auto_create_tiles->set_text(TTR("The atlas's texture was modified.\nWould you like to automatically create tiles in the atlas?"));
+ confirm_auto_create_tiles->get_ok_button()->set_text(TTR("Yes"));
+ confirm_auto_create_tiles->add_cancel_button()->set_text(TTR("No"));
+ confirm_auto_create_tiles->connect("confirmed", callable_mp(this, &TileSetAtlasSourceEditor::_auto_create_tiles));
+ add_child(confirm_auto_create_tiles);
+
+ // -- Toolbox --
+ tools_button_group.instance();
+
+ toolbox = memnew(HBoxContainer);
+ right_panel->add_child(toolbox);
+
+ tool_select_button = memnew(Button);
+ tool_select_button->set_flat(true);
+ tool_select_button->set_toggle_mode(true);
+ tool_select_button->set_pressed(true);
+ tool_select_button->set_button_group(tools_button_group);
+ tool_select_button->set_tooltip(TTR("Select tiles."));
+ tool_select_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles));
+ tool_select_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_id_label));
+ tool_select_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_inspector));
+ tool_select_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view));
+ tool_select_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_toolbar));
+ toolbox->add_child(tool_select_button);
+
+ tool_add_remove_button = memnew(Button);
+ tool_add_remove_button->set_flat(true);
+ tool_add_remove_button->set_toggle_mode(true);
+ tool_add_remove_button->set_button_group(tools_button_group);
+ tool_add_remove_button->set_tooltip(TTR("Add/Remove tiles tool (use the shift key to create big tiles)."));
+ tool_add_remove_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles));
+ tool_add_remove_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_id_label));
+ tool_add_remove_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_inspector));
+ tool_add_remove_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view));
+ tool_add_remove_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_toolbar));
+ toolbox->add_child(tool_add_remove_button);
+
+ tool_add_remove_rect_button = memnew(Button);
+ tool_add_remove_rect_button->set_flat(true);
+ tool_add_remove_rect_button->set_toggle_mode(true);
+ tool_add_remove_rect_button->set_button_group(tools_button_group);
+ tool_add_remove_rect_button->set_tooltip(TTR("Add/Remove tiles rectangle tool (use the shift key to create big tiles)."));
+ tool_add_remove_rect_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles));
+ tool_add_remove_rect_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_id_label));
+ tool_add_remove_rect_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_inspector));
+ tool_add_remove_rect_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view));
+ tool_add_remove_rect_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_toolbar));
+ toolbox->add_child(tool_add_remove_rect_button);
+
+ // Tool settings.
+ tool_settings = memnew(HBoxContainer);
+ toolbox->add_child(tool_settings);
+
+ tool_settings_vsep = memnew(VSeparator);
+ tool_settings->add_child(tool_settings_vsep);
+
+ tools_settings_erase_button = memnew(Button);
+ tools_settings_erase_button->set_flat(true);
+ tools_settings_erase_button->set_toggle_mode(true);
+ tools_settings_erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", KEY_E));
+ tools_settings_erase_button->set_shortcut_context(this);
+ tool_settings->add_child(tools_settings_erase_button);
+
+ VSeparator *tool_advanced_vsep = memnew(VSeparator);
+ toolbox->add_child(tool_advanced_vsep);
+
+ tool_advanced_menu_buttom = memnew(MenuButton);
+ tool_advanced_menu_buttom->set_flat(true);
+ tool_advanced_menu_buttom->get_popup()->add_item(TTR("Cleanup Tiles Outside Texture"), ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE);
+ tool_advanced_menu_buttom->get_popup()->set_item_disabled(0, true);
+ tool_advanced_menu_buttom->get_popup()->add_item(TTR("Create Tiles in Non-Transparent Texture Regions"), ADVANCED_AUTO_CREATE_TILES);
+ tool_advanced_menu_buttom->get_popup()->add_item(TTR("Remove Tiles in Fully Transparent Texture Regions"), ADVANCED_AUTO_REMOVE_TILES);
+ tool_advanced_menu_buttom->get_popup()->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option));
+ toolbox->add_child(tool_advanced_menu_buttom);
+
+ _update_toolbar();
+
+ // Right side of toolbar.
+ Control *middle_space = memnew(Control);
+ middle_space->set_h_size_flags(SIZE_EXPAND_FILL);
+ toolbox->add_child(middle_space);
+
+ tool_tile_id_label = memnew(Label);
+ tool_tile_id_label->set_mouse_filter(Control::MOUSE_FILTER_STOP);
+ toolbox->add_child(tool_tile_id_label);
+ _update_tile_id_label();
+
+ // Tile atlas view.
+ 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->connect("transform_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_view_transform));
+ tile_atlas_view->connect("transform_changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_view_transform_changed).unbind(2));
+ right_panel->add_child(tile_atlas_view);
+
+ base_tile_popup_menu = memnew(PopupMenu);
+ base_tile_popup_menu->add_shortcut(ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), KEY_DELETE), TILE_DELETE);
+ base_tile_popup_menu->add_item(TTR("Create an Alternative Tile"), TILE_CREATE_ALTERNATIVE);
+ base_tile_popup_menu->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option));
+ tile_atlas_view->add_child(base_tile_popup_menu);
+
+ empty_base_tile_popup_menu = memnew(PopupMenu);
+ empty_base_tile_popup_menu->add_item(TTR("Create a Tile"), TILE_CREATE);
+ empty_base_tile_popup_menu->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option));
+ tile_atlas_view->add_child(empty_base_tile_popup_menu);
+
+ tile_atlas_control = memnew(Control);
+ tile_atlas_control->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_draw));
+ tile_atlas_control->connect("mouse_exited", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_mouse_exited));
+ tile_atlas_control->connect("gui_input", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_gui_input));
+ tile_atlas_view->add_control_over_atlas_tiles(tile_atlas_control);
+
+ tile_atlas_control_unscaled = memnew(Control);
+ tile_atlas_control_unscaled->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ tile_atlas_control_unscaled->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_unscaled_draw));
+ tile_atlas_view->add_control_over_atlas_tiles(tile_atlas_control_unscaled, false);
+ tile_atlas_control_unscaled->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+
+ alternative_tile_popup_menu = memnew(PopupMenu);
+ alternative_tile_popup_menu->add_shortcut(ED_SHORTCUT("tiles_editor/delete_tile", TTR("Delete"), KEY_DELETE), TILE_DELETE);
+ alternative_tile_popup_menu->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option));
+ tile_atlas_view->add_child(alternative_tile_popup_menu);
+
+ alternative_tiles_control = memnew(Control);
+ alternative_tiles_control->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_draw));
+ alternative_tiles_control->connect("mouse_exited", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_mouse_exited));
+ alternative_tiles_control->connect("gui_input", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input));
+ tile_atlas_view->add_control_over_alternative_tiles(alternative_tiles_control);
+
+ alternative_tiles_control_unscaled = memnew(Control);
+ alternative_tiles_control_unscaled->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ alternative_tiles_control_unscaled->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw));
+ tile_atlas_view->add_control_over_atlas_tiles(alternative_tiles_control_unscaled, false);
+ alternative_tiles_control_unscaled->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+
+ tile_atlas_view_missing_source_label = memnew(Label);
+ tile_atlas_view_missing_source_label->set_text(TTR("Add or select an atlas texture to the left panel."));
+ tile_atlas_view_missing_source_label->set_align(Label::ALIGN_CENTER);
+ tile_atlas_view_missing_source_label->set_valign(Label::VALIGN_CENTER);
+ tile_atlas_view_missing_source_label->set_h_size_flags(SIZE_EXPAND_FILL);
+ tile_atlas_view_missing_source_label->set_v_size_flags(SIZE_EXPAND_FILL);
+ tile_atlas_view_missing_source_label->hide();
+ right_panel->add_child(tile_atlas_view_missing_source_label);
+}
+
+TileSetAtlasSourceEditor::~TileSetAtlasSourceEditor() {
+ memdelete(tile_proxy_object);
+ memdelete(atlas_source_proxy_object);
+}
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h
new file mode 100644
index 0000000000..ff68aa8288
--- /dev/null
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h
@@ -0,0 +1,260 @@
+/*************************************************************************/
+/* tile_set_atlas_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_ATLAS_SOURCE_EDITOR_H
+#define TILE_SET_ATLAS_SOURCE_EDITOR_H
+
+#include "tile_atlas_view.h"
+
+#include "editor/editor_node.h"
+#include "scene/gui/split_container.h"
+#include "scene/resources/tile_set.h"
+
+class TileSet;
+
+class TileSetAtlasSourceEditor : public HBoxContainer {
+ GDCLASS(TileSetAtlasSourceEditor, HBoxContainer);
+
+private:
+ // A class to store which tiles are selected.
+ struct TileSelection {
+ Vector2i tile = TileSetAtlasSource::INVALID_ATLAS_COORDS;
+ int alternative = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
+
+ bool operator<(const TileSelection &p_other) const {
+ if (tile == p_other.tile) {
+ return alternative < p_other.alternative;
+ } else {
+ return tile < p_other.tile;
+ }
+ }
+ };
+
+ // -- Proxy object for an atlas source, needed by the inspector --
+ class TileSetAtlasSourceProxyObject : public Object {
+ GDCLASS(TileSetAtlasSourceProxyObject, Object);
+
+ private:
+ Ref<TileSet> tile_set;
+ TileSetAtlasSource *tile_set_atlas_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;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+ static void _bind_methods();
+
+ public:
+ void set_id(int p_id);
+ int get_id();
+
+ void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id);
+ };
+
+ // -- Proxy object for a tile, needed by the inspector --
+ class TileProxyObject : public Object {
+ GDCLASS(TileProxyObject, Object);
+
+ private:
+ TileSetAtlasSourceEditor *tiles_set_atlas_source_editor;
+
+ TileSetAtlasSource *tile_set_atlas_source = nullptr;
+ int source_id;
+ Set<TileSelection> tiles = Set<TileSelection>();
+
+ 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(TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id = -1, Set<TileSelection> p_tiles = Set<TileSelection>());
+
+ TileProxyObject(TileSetAtlasSourceEditor *p_tiles_editor_source_tab) {
+ tiles_set_atlas_source_editor = p_tiles_editor_source_tab;
+ }
+ };
+
+ Ref<TileSet> tile_set;
+ TileSetAtlasSource *tile_set_atlas_source = nullptr;
+ int tile_set_atlas_source_id = -1;
+
+ UndoRedo *undo_redo = EditorNode::get_undo_redo();
+
+ bool tile_set_atlas_source_changed_needs_update = false;
+
+ // -- Inspector --
+ TileProxyObject *tile_proxy_object;
+ Label *tile_inspector_label;
+ EditorInspector *tile_inspector;
+ String selected_property;
+ void _inspector_property_selected(String p_property);
+
+ TileSetAtlasSourceProxyObject *atlas_source_proxy_object;
+ Label *atlas_source_inspector_label;
+ EditorInspector *atlas_source_inspector;
+
+ // -- Atlas view --
+ HBoxContainer *toolbox;
+ Label *tile_atlas_view_missing_source_label;
+ TileAtlasView *tile_atlas_view;
+
+ // Dragging
+ enum DragType {
+ DRAG_TYPE_NONE = 0,
+ DRAG_TYPE_CREATE_TILES,
+ DRAG_TYPE_CREATE_TILES_USING_RECT,
+ DRAG_TYPE_CREATE_BIG_TILE,
+ DRAG_TYPE_REMOVE_TILES,
+ DRAG_TYPE_REMOVE_TILES_USING_RECT,
+
+ DRAG_TYPE_MOVE_TILE,
+
+ DRAG_TYPE_RECT_SELECT,
+
+ // Warning: keep in this order.
+ DRAG_TYPE_RESIZE_TOP_LEFT,
+ DRAG_TYPE_RESIZE_TOP,
+ DRAG_TYPE_RESIZE_TOP_RIGHT,
+ DRAG_TYPE_RESIZE_RIGHT,
+ DRAG_TYPE_RESIZE_BOTTOM_RIGHT,
+ DRAG_TYPE_RESIZE_BOTTOM,
+ DRAG_TYPE_RESIZE_BOTTOM_LEFT,
+ DRAG_TYPE_RESIZE_LEFT,
+ };
+ DragType drag_type = DRAG_TYPE_NONE;
+ Vector2i drag_start_mouse_pos;
+ Vector2i drag_last_mouse_pos;
+ Vector2i drag_current_tile;
+
+ Rect2i drag_start_tile_shape;
+ Set<Vector2i> drag_modified_tiles;
+ void _end_dragging();
+
+ Map<Vector2i, List<const PropertyInfo *>> _group_properties_per_tiles(const List<PropertyInfo> &r_list, const TileSetAtlasSource *p_atlas);
+
+ // Popup functions.
+ enum MenuOptions {
+ TILE_CREATE,
+ TILE_CREATE_ALTERNATIVE,
+ TILE_DELETE,
+
+ ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE,
+ ADVANCED_AUTO_CREATE_TILES,
+ ADVANCED_AUTO_REMOVE_TILES,
+ };
+ Vector2i menu_option_coords;
+ int menu_option_alternative = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
+ void _menu_option(int p_option);
+
+ // Tool buttons.
+ Ref<ButtonGroup> tools_button_group;
+ Button *tool_select_button;
+ Button *tool_add_remove_button;
+ Button *tool_add_remove_rect_button;
+ Label *tool_tile_id_label;
+
+ HBoxContainer *tool_settings;
+ VSeparator *tool_settings_vsep;
+ Button *tools_settings_erase_button;
+
+ MenuButton *tool_advanced_menu_buttom;
+
+ // Selection.
+ Set<TileSelection> selection;
+
+ void _set_selection_from_array(Array p_selection);
+ 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;
+
+ PopupMenu *base_tile_popup_menu;
+ PopupMenu *empty_base_tile_popup_menu;
+ Ref<Texture2D> resize_handle;
+ Ref<Texture2D> resize_handle_disabled;
+ Control *tile_atlas_control;
+ Control *tile_atlas_control_unscaled;
+ void _tile_atlas_control_draw();
+ void _tile_atlas_control_unscaled_draw();
+ void _tile_atlas_control_mouse_exited();
+ void _tile_atlas_control_gui_input(const Ref<InputEvent> &p_event);
+ 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);
+
+ PopupMenu *alternative_tile_popup_menu;
+ Control *alternative_tiles_control;
+ Control *alternative_tiles_control_unscaled;
+ void _tile_alternatives_control_draw();
+ void _tile_alternatives_control_unscaled_draw();
+ void _tile_alternatives_control_mouse_exited();
+ void _tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event);
+
+ // -- Update functions --
+ void _update_tile_id_label();
+ void _update_source_inspector();
+ void _update_fix_selected_and_hovered_tiles();
+ void _update_tile_inspector();
+ void _update_manage_tile_properties_button();
+ void _update_atlas_view();
+ void _update_toolbar();
+
+ // -- input events --
+ void _unhandled_key_input(const Ref<InputEvent> &p_event);
+
+ // -- Misc --
+ void _auto_create_tiles();
+ void _auto_remove_tiles();
+ AcceptDialog *confirm_auto_create_tiles;
+
+ void _tile_set_atlas_source_changed();
+ void _atlas_source_proxy_object_changed(String p_what);
+
+ void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value);
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_source, int p_source_id);
+ void init_source();
+
+ TileSetAtlasSourceEditor();
+ ~TileSetAtlasSourceEditor();
+};
+
+#endif // TILE_SET_ATLAS_SOURCE_EDITOR_H
diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp
new file mode 100644
index 0000000000..8a5890e9a4
--- /dev/null
+++ b/editor/plugins/tiles/tile_set_editor.cpp
@@ -0,0 +1,520 @@
+/*************************************************************************/
+/* tile_set_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_editor.h"
+
+#include "tile_data_editors.h"
+#include "tiles_editor_plugin.h"
+
+#include "editor/editor_scale.h"
+
+#include "scene/gui/box_container.h"
+#include "scene/gui/control.h"
+#include "scene/gui/tab_container.h"
+
+TileSetEditor *TileSetEditor::singleton = nullptr;
+
+void TileSetEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ if (!can_drop_data_fw(p_point, p_data, p_from)) {
+ return;
+ }
+
+ if (p_from == sources_list) {
+ // Handle dropping a texture in the list of atlas resources.
+ int source_id = -1;
+ int added = 0;
+ Dictionary d = p_data;
+ Vector<String> files = d["files"];
+ for (int i = 0; i < files.size(); i++) {
+ Ref<Texture2D> resource = ResourceLoader::load(files[i]);
+ if (resource.is_valid()) {
+ // Retrieve the id for the next created source.
+ source_id = tile_set->get_next_source_id();
+
+ // Actually create the new source.
+ Ref<TileSetAtlasSource> atlas_source = memnew(TileSetAtlasSource);
+ atlas_source->set_texture(resource);
+ undo_redo->create_action(TTR("Add a new 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();
+ added += 1;
+ }
+ }
+
+ if (added == 1) {
+ tile_set_atlas_source_editor->init_source();
+ }
+
+ // Update the selected source (thus trigerring an update).
+ _update_atlas_sources_list(source_id);
+ }
+}
+
+bool TileSetEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
+ ERR_FAIL_COND_V(!tile_set.is_valid(), false);
+
+ if (p_from == sources_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, "Texture2D")) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+ return false;
+}
+
+void TileSetEditor::_update_atlas_sources_list(int force_selected_id) {
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ // Get the previously selected id.
+ int old_selected = -1;
+ if (sources_list->get_current() >= 0) {
+ int source_id = sources_list->get_item_metadata(sources_list->get_current());
+ if (tile_set->has_source(source_id)) {
+ old_selected = source_id;
+ }
+ }
+
+ int to_select = -1;
+ if (force_selected_id >= 0) {
+ to_select = force_selected_id;
+ } else if (old_selected >= 0) {
+ to_select = old_selected;
+ }
+
+ // Clear the list.
+ sources_list->clear();
+
+ // Update the atlas sources.
+ 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);
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ Ref<Texture2D> 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);
+ } else {
+ sources_list->add_item(vformat("No texture atlas source - (id:%d)", source_id), missing_texture_texture);
+ }
+ } 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);
+ }
+
+ // Set again the current selected item if needed.
+ if (to_select >= 0) {
+ for (int i = 0; i < sources_list->get_item_count(); i++) {
+ if ((int)sources_list->get_item_metadata(i) == to_select) {
+ sources_list->set_current(i);
+ if (old_selected != to_select) {
+ sources_list->emit_signal("item_selected", sources_list->get_current());
+ }
+ break;
+ }
+ }
+ }
+
+ // If nothing is selected, select the first entry.
+ if (sources_list->get_current() < 0 && sources_list->get_item_count() > 0) {
+ sources_list->set_current(0);
+ if (old_selected != int(sources_list->get_item_metadata(0))) {
+ sources_list->emit_signal("item_selected", sources_list->get_current());
+ }
+ }
+
+ // If there is no source left, hide all editors and show the label.
+ _source_selected(sources_list->get_current());
+
+ // Synchronize the lists.
+ TilesEditor::get_singleton()->set_atlas_sources_lists_current(sources_list->get_current());
+}
+
+void TileSetEditor::_source_selected(int p_source_index) {
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ // Update the selected source.
+ sources_delete_button->set_disabled(p_source_index < 0);
+
+ 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));
+ 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->show();
+ } else {
+ no_source_selected_label->show();
+ tile_set_atlas_source_editor->hide();
+ }
+ } else {
+ no_source_selected_label->show();
+ tile_set_atlas_source_editor->hide();
+ }
+}
+
+void TileSetEditor::_source_add_pressed() {
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ int source_id = tile_set->get_next_source_id();
+
+ 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();
+
+ _update_atlas_sources_list(source_id);
+}
+
+void TileSetEditor::_source_delete_pressed() {
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ // Update the selected source.
+ int to_delete = sources_list->get_item_metadata(sources_list->get_current());
+
+ Ref<TileSetSource> source = tile_set->get_source(to_delete);
+
+ // Remove the source.
+ undo_redo->create_action(TTR("Remove source"));
+ undo_redo->add_do_method(*tile_set, "remove_source", to_delete);
+ undo_redo->add_undo_method(*tile_set, "add_source", source, to_delete);
+ undo_redo->commit_action();
+
+ _update_atlas_sources_list();
+}
+
+void TileSetEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED:
+ sources_delete_button->set_icon(get_theme_icon("Remove", "EditorIcons"));
+ sources_add_button->set_icon(get_theme_icon("Add", "EditorIcons"));
+ missing_texture_texture = get_theme_icon("TileSet", "EditorIcons");
+ break;
+ case NOTIFICATION_INTERNAL_PROCESS:
+ if (tile_set_changed_needs_update) {
+ _update_atlas_sources_list();
+ tile_set_changed_needs_update = false;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void TileSetEditor::_tile_set_changed() {
+ tile_set_changed_needs_update = true;
+}
+
+void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) {
+ UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo);
+ ERR_FAIL_COND(!undo_redo);
+
+#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property));
+ TileSet *tile_set = Object::cast_to<TileSet>(p_edited);
+ if (tile_set) {
+ Vector<String> components = p_property.split("/", true, 3);
+ for (int i = 0; i < tile_set->get_source_count(); i++) {
+ int source_id = tile_set->get_source_id(i);
+
+ Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id);
+ if (tas.is_valid()) {
+ for (int j = 0; j < tas->get_tiles_count(); j++) {
+ Vector2i tile_id = tas->get_tile_id(j);
+ for (int k = 0; k < tas->get_alternative_tiles_count(tile_id); k++) {
+ int alternative_id = tas->get_alternative_tile_id(tile_id, k);
+ TileData *tile_data = Object::cast_to<TileData>(tas->get_tile_data(tile_id, alternative_id));
+ ERR_FAIL_COND(!tile_data);
+
+ if (p_property == "occlusion_layers_count") {
+ int new_layer_count = p_new_value;
+ int old_layer_count = tile_set->get_occlusion_layers_count();
+ if (new_layer_count < old_layer_count) {
+ for (int occclusion_layer_index = new_layer_count - 1; occclusion_layer_index < old_layer_count; occclusion_layer_index++) {
+ ADD_UNDO(tile_data, vformat("occlusion_layer_%d/polygon", occclusion_layer_index));
+ }
+ }
+ } else if (p_property == "physics_layers_count") {
+ int new_layer_count = p_new_value;
+ int old_layer_count = tile_set->get_physics_layers_count();
+ if (new_layer_count < old_layer_count) {
+ for (int physics_layer_index = new_layer_count - 1; physics_layer_index < old_layer_count; physics_layer_index++) {
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/shapes_count", physics_layer_index));
+ for (int shape_index = 0; shape_index < tile_data->get_collision_shapes_count(physics_layer_index); shape_index++) {
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/shape", physics_layer_index, shape_index));
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/one_way", physics_layer_index, shape_index));
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/one_way_margin", physics_layer_index, shape_index));
+ }
+ }
+ }
+ } else if ((p_property == "terrains_sets_count" && tile_data->get_terrain_set() >= (int)p_new_value) ||
+ (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_integer() && components[1] == "mode") ||
+ (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_integer() && components[1] == "terrains_count" && tile_data->get_terrain_set() == components[0].trim_prefix("terrain_set_").to_int() && (int)p_new_value < tile_set->get_terrains_count(tile_data->get_terrain_set()))) {
+ ADD_UNDO(tile_data, "terrain_set");
+ if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/right_side");
+ }
+ if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_CORNER)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/right_corner");
+ }
+ if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/bottom_right_side");
+ }
+ if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/bottom_right_corner");
+ }
+ if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/bottom_side");
+ }
+ if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/bottom_corner");
+ }
+ if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/bottom_left_side");
+ }
+ if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/bottom_left_corner");
+ }
+ if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/left_side");
+ }
+ if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_CORNER)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/left_corner");
+ }
+ if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/top_left_side");
+ }
+ if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/top_left_corner");
+ }
+ if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/top_side");
+ }
+ if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_CORNER)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/top_corner");
+ }
+ if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/top_right_side");
+ }
+ if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/top_right_corner");
+ }
+ } else if (p_property == "navigation_layers_count") {
+ int new_layer_count = p_new_value;
+ int old_layer_count = tile_set->get_navigation_layers_count();
+ if (new_layer_count < old_layer_count) {
+ for (int navigation_layer_index = new_layer_count - 1; navigation_layer_index < old_layer_count; navigation_layer_index++) {
+ ADD_UNDO(tile_data, vformat("navigation_layer_%d/polygon", navigation_layer_index));
+ }
+ }
+ } else if (p_property == "custom_data_layers_count") {
+ int new_layer_count = p_new_value;
+ int old_layer_count = tile_set->get_custom_data_layers_count();
+ if (new_layer_count < old_layer_count) {
+ for (int custom_data_layer_index = new_layer_count - 1; custom_data_layer_index < old_layer_count; custom_data_layer_index++) {
+ ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer_index));
+ }
+ }
+ } else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_integer() && components[1] == "type") {
+ int custom_data_layer = components[0].trim_prefix("custom_data_layer_").is_valid_integer();
+ ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer));
+ }
+ }
+ }
+ }
+ }
+ }
+#undef ADD_UNDO
+}
+
+void TileSetEditor::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &TileSetEditor::can_drop_data_fw);
+ ClassDB::bind_method(D_METHOD("drop_data_fw"), &TileSetEditor::drop_data_fw);
+}
+
+TileDataEditor *TileSetEditor::get_tile_data_editor(String p_property) {
+ Vector<String> components = String(p_property).split("/", true);
+
+ if (p_property == "z_index") {
+ return tile_data_integer_editor;
+ } else if (p_property == "probability") {
+ return tile_data_float_editor;
+ } else if (p_property == "y_sort_origin") {
+ return tile_data_position_editor;
+ } else if (p_property == "texture_offset") {
+ return tile_data_texture_offset_editor;
+ } else if (components.size() >= 1 && components[0].begins_with("occlusion_layer_")) {
+ return tile_data_occlusion_shape_editor;
+ } else if (components.size() >= 1 && components[0].begins_with("physics_layer_")) {
+ return tile_data_collision_shape_editor;
+ } else if (p_property == "mode" || p_property == "terrain" || (components.size() >= 1 && components[0] == "terrains_peering_bit")) {
+ return tile_data_terrains_editor;
+ } else if (components.size() >= 1 && components[0].begins_with("navigation_layer_")) {
+ return tile_data_navigation_polygon_editor;
+ }
+
+ return nullptr;
+}
+
+void TileSetEditor::edit(Ref<TileSet> p_tile_set) {
+ if (p_tile_set == tile_set) {
+ return;
+ }
+
+ // Remove listener.
+ if (tile_set.is_valid()) {
+ tile_set->disconnect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed));
+ }
+
+ // Change the edited object.
+ tile_set = p_tile_set;
+
+ // Add the listener again.
+ if (tile_set.is_valid()) {
+ tile_set->connect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed));
+ _update_atlas_sources_list();
+ }
+
+ tile_set_atlas_source_editor->hide();
+ no_source_selected_label->show();
+}
+
+TileSetEditor::TileSetEditor() {
+ singleton = this;
+
+ set_process_internal(true);
+
+ // Split container.
+ HSplitContainer *split_container = memnew(HSplitContainer);
+ split_container->set_name(TTR("Tiles"));
+ split_container->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_container->set_v_size_flags(SIZE_EXPAND_FILL);
+ add_child(split_container);
+
+ // Sources list.
+ VBoxContainer *split_container_left_side = memnew(VBoxContainer);
+ split_container_left_side->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_container_left_side->set_v_size_flags(SIZE_EXPAND_FILL);
+ split_container_left_side->set_stretch_ratio(0.25);
+ split_container_left_side->set_custom_minimum_size(Size2i(70, 0) * EDSCALE);
+ split_container->add_child(split_container_left_side);
+
+ sources_list = memnew(ItemList);
+ sources_list->set_fixed_icon_size(Size2i(60, 60) * EDSCALE);
+ sources_list->set_h_size_flags(SIZE_EXPAND_FILL);
+ sources_list->set_v_size_flags(SIZE_EXPAND_FILL);
+ 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_drag_forwarding(this);
+ split_container_left_side->add_child(sources_list);
+
+ HBoxContainer *sources_bottom_actions = memnew(HBoxContainer);
+ sources_bottom_actions->set_alignment(HBoxContainer::ALIGN_END);
+ split_container_left_side->add_child(sources_bottom_actions);
+
+ sources_delete_button = memnew(Button);
+ sources_delete_button->set_flat(true);
+ sources_delete_button->set_disabled(true);
+ 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->set_flat(true);
+ sources_add_button->connect("pressed", callable_mp(this, &TileSetEditor::_source_add_pressed));
+ sources_bottom_actions->add_child(sources_add_button);
+
+ // 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."));
+ no_source_selected_label->set_h_size_flags(SIZE_EXPAND_FILL);
+ 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);
+
+ // 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);
+ tile_set_atlas_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));
+}
+
+TileSetEditor::~TileSetEditor() {
+ if (tile_set.is_valid()) {
+ tile_set->disconnect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed));
+ }
+
+ // Delete tile data editors.
+ memdelete(tile_data_texture_offset_editor);
+ memdelete(tile_data_position_editor);
+ memdelete(tile_data_integer_editor);
+ memdelete(tile_data_float_editor);
+ memdelete(tile_data_occlusion_shape_editor);
+ memdelete(tile_data_collision_shape_editor);
+ memdelete(tile_data_terrains_editor);
+ memdelete(tile_data_navigation_polygon_editor);
+}
diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h
new file mode 100644
index 0000000000..c4aebb40a2
--- /dev/null
+++ b/editor/plugins/tiles/tile_set_editor.h
@@ -0,0 +1,94 @@
+/*************************************************************************/
+/* tile_set_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_EDITOR_H
+#define TILE_SET_EDITOR_H
+
+#include "scene/gui/box_container.h"
+#include "scene/resources/tile_set.h"
+#include "tile_data_editors.h"
+#include "tile_set_atlas_source_editor.h"
+
+class TileSetEditor : public VBoxContainer {
+ GDCLASS(TileSetEditor, VBoxContainer);
+
+ static TileSetEditor *singleton;
+
+private:
+ Ref<TileSet> tile_set;
+ bool tile_set_changed_needs_update = false;
+
+ Label *no_source_selected_label;
+ TileSetAtlasSourceEditor *tile_set_atlas_source_editor;
+
+ UndoRedo *undo_redo = EditorNode::get_undo_redo();
+
+ void _update_atlas_sources_list(int force_selected_id = -1);
+
+ // List of tile data editors.
+ TileDataTextureOffsetEditor *tile_data_texture_offset_editor = memnew(TileDataTextureOffsetEditor);
+ TileDataPositionEditor *tile_data_position_editor = memnew(TileDataPositionEditor);
+ TileDataIntegerEditor *tile_data_integer_editor = memnew(TileDataIntegerEditor);
+ TileDataFloatEditor *tile_data_float_editor = memnew(TileDataFloatEditor);
+ TileDataOcclusionShapeEditor *tile_data_occlusion_shape_editor = memnew(TileDataOcclusionShapeEditor);
+ TileDataCollisionShapeEditor *tile_data_collision_shape_editor = memnew(TileDataCollisionShapeEditor);
+ TileDataTerrainsEditor *tile_data_terrains_editor = memnew(TileDataTerrainsEditor);
+ TileDataNavigationPolygonEditor *tile_data_navigation_polygon_editor = memnew(TileDataNavigationPolygonEditor);
+
+ // -- Sources management --
+ Button *sources_delete_button;
+ Button *sources_add_button;
+ ItemList *sources_list;
+ Ref<Texture2D> missing_texture_texture;
+ void _source_selected(int p_source_index);
+ void _source_add_pressed();
+ void _source_delete_pressed();
+
+ void _tile_set_changed();
+
+ void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value);
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ _FORCE_INLINE_ static TileSetEditor *get_singleton() { return singleton; }
+
+ TileDataEditor *get_tile_data_editor(String property);
+ void edit(Ref<TileSet> p_tile_set);
+ 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;
+
+ TileSetEditor();
+ ~TileSetEditor();
+};
+
+#endif // TILE_SET_EDITOR_PLUGIN_H
diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp
new file mode 100644
index 0000000000..971ff15073
--- /dev/null
+++ b/editor/plugins/tiles/tiles_editor_plugin.cpp
@@ -0,0 +1,276 @@
+/*************************************************************************/
+/* tiles_editor_plugin.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 "tiles_editor_plugin.h"
+
+#include "editor/editor_node.h"
+#include "editor/editor_scale.h"
+#include "editor/plugins/canvas_item_editor_plugin.h"
+
+#include "scene/2d/tile_map.h"
+#include "scene/resources/tile_set.h"
+
+#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+#include "scene/gui/control.h"
+#include "scene/gui/separator.h"
+
+#include "tile_set_editor.h"
+
+TilesEditor *TilesEditor::singleton = nullptr;
+
+void TilesEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ tileset_tilemap_switch_button->set_icon(get_theme_icon("TileSet", "EditorIcons"));
+ } break;
+ case NOTIFICATION_INTERNAL_PROCESS: {
+ if (tile_map_changed_needs_update) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (tile_map) {
+ tile_set = tile_map->get_tileset();
+ }
+ _update_switch_button();
+ _update_editors();
+ }
+ } break;
+ }
+}
+
+void TilesEditor::_tile_map_changed() {
+ tile_map_changed_needs_update = true;
+}
+
+void TilesEditor::_update_switch_button() {
+ // Force the buttons status if needed.
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (tile_map && !tile_set.is_valid()) {
+ tileset_tilemap_switch_button->set_pressed(false);
+ } else if (!tile_map && tile_set.is_valid()) {
+ tileset_tilemap_switch_button->set_pressed(true);
+ }
+}
+
+void TilesEditor::_update_editors() {
+ // Set editors visibility.
+ tilemap_toolbar->set_visible(!tileset_tilemap_switch_button->is_pressed());
+ tilemap_editor->set_visible(!tileset_tilemap_switch_button->is_pressed());
+ tileset_editor->set_visible(tileset_tilemap_switch_button->is_pressed());
+
+ // Enable/disable the switch button.
+ if (!tileset_tilemap_switch_button->is_pressed()) {
+ if (!tile_set.is_valid()) {
+ tileset_tilemap_switch_button->set_disabled(true);
+ tileset_tilemap_switch_button->set_tooltip(TTR("This TileMap has no assigned TileSet, assign a TileSet to this TileMap to edit it."));
+ } else {
+ tileset_tilemap_switch_button->set_disabled(false);
+ tileset_tilemap_switch_button->set_tooltip(TTR("Switch between TileSet/TileMap editor."));
+ }
+ } else {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ tileset_tilemap_switch_button->set_disabled(true);
+ tileset_tilemap_switch_button->set_tooltip(TTR("You are editing a TileSet resource. Select a TileMap node to paint."));
+ } else {
+ tileset_tilemap_switch_button->set_disabled(false);
+ tileset_tilemap_switch_button->set_tooltip(TTR("Switch between TileSet/TileMap editor."));
+ }
+ }
+
+ // If tile_map is not edited, we change the edited only if we are not editing a tile_set.
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (tile_map) {
+ tilemap_editor->edit(tile_map);
+ } else {
+ tilemap_editor->edit(nullptr);
+ }
+ tileset_editor->edit(tile_set);
+
+ // Update the viewport
+ CanvasItemEditor::get_singleton()->update_viewport();
+}
+
+void TilesEditor::set_atlas_sources_lists_current(int p_current) {
+ atlas_sources_lists_current = p_current;
+}
+
+void TilesEditor::synchronize_atlas_sources_list(Object *p_current) {
+ ItemList *item_list = Object::cast_to<ItemList>(p_current);
+ ERR_FAIL_COND(!item_list);
+
+ if (item_list->is_visible_in_tree()) {
+ if (atlas_sources_lists_current < 0 || atlas_sources_lists_current >= item_list->get_item_count()) {
+ item_list->deselect_all();
+ } else {
+ item_list->set_current(atlas_sources_lists_current);
+ item_list->emit_signal("item_selected", atlas_sources_lists_current);
+ }
+ }
+}
+
+void TilesEditor::set_atlas_view_transform(float p_zoom, Vector2 p_scroll) {
+ atlas_view_zoom = p_zoom;
+ atlas_view_scroll = p_scroll;
+}
+
+void TilesEditor::synchronize_atlas_view(Object *p_current) {
+ TileAtlasView *tile_atlas_view = Object::cast_to<TileAtlasView>(p_current);
+ ERR_FAIL_COND(!tile_atlas_view);
+
+ if (tile_atlas_view->is_visible_in_tree()) {
+ tile_atlas_view->set_transform(atlas_view_zoom, Vector2(atlas_view_scroll.x, atlas_view_scroll.y));
+ }
+}
+
+void TilesEditor::edit(Object *p_object) {
+ // Disconnect to changes.
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (tile_map) {
+ tile_map->disconnect("changed", callable_mp(this, &TilesEditor::_tile_map_changed));
+ }
+
+ // Update edited objects.
+ tile_set = Ref<TileSet>();
+ if (p_object) {
+ if (p_object->is_class("TileMap")) {
+ tile_map_id = p_object->get_instance_id();
+ tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ tile_set = tile_map->get_tileset();
+ } else if (p_object->is_class("TileSet")) {
+ tile_set = Ref<TileSet>(p_object);
+ if (tile_map) {
+ if (tile_map->get_tileset() != tile_set) {
+ tile_map = nullptr;
+ }
+ }
+ }
+
+ // Update pressed status button.
+ if (p_object->is_class("TileMap")) {
+ tileset_tilemap_switch_button->set_pressed(false);
+ } else if (p_object->is_class("TileSet")) {
+ tileset_tilemap_switch_button->set_pressed(true);
+ }
+ }
+
+ // Update the editors.
+ _update_switch_button();
+ _update_editors();
+
+ // Add change listener.
+ if (tile_map) {
+ tile_map->connect("changed", callable_mp(this, &TilesEditor::_tile_map_changed));
+ }
+}
+
+void TilesEditor::_bind_methods() {
+}
+
+TilesEditor::TilesEditor(EditorNode *p_editor) {
+ set_process_internal(true);
+
+ // Update the singleton.
+ singleton = this;
+
+ // Toolbar.
+ HBoxContainer *toolbar = memnew(HBoxContainer);
+ add_child(toolbar);
+
+ // Switch button.
+ tileset_tilemap_switch_button = memnew(Button);
+ tileset_tilemap_switch_button->set_flat(true);
+ tileset_tilemap_switch_button->set_toggle_mode(true);
+ tileset_tilemap_switch_button->connect("toggled", callable_mp(this, &TilesEditor::_update_editors).unbind(1));
+ toolbar->add_child(tileset_tilemap_switch_button);
+
+ // Tilemap editor.
+ tilemap_editor = memnew(TileMapEditor);
+ tilemap_editor->set_h_size_flags(SIZE_EXPAND_FILL);
+ tilemap_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+ tilemap_editor->hide();
+ add_child(tilemap_editor);
+
+ tilemap_toolbar = tilemap_editor->get_toolbar();
+ toolbar->add_child(tilemap_toolbar);
+
+ // Tileset editor.
+ tileset_editor = memnew(TileSetEditor);
+ tileset_editor->set_h_size_flags(SIZE_EXPAND_FILL);
+ tileset_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+ tileset_editor->hide();
+ add_child(tileset_editor);
+
+ // Initialization.
+ _update_switch_button();
+ _update_editors();
+}
+
+TilesEditor::~TilesEditor() {
+}
+
+///////////////////////////////////////////////////////////////
+
+void TilesEditorPlugin::_notification(int p_what) {
+}
+
+void TilesEditorPlugin::make_visible(bool p_visible) {
+ if (p_visible) {
+ tiles_editor_button->show();
+ editor_node->make_bottom_panel_item_visible(tiles_editor);
+ //get_tree()->connect_compat("idle_frame", tileset_editor, "_on_workspace_process");
+ } else {
+ editor_node->hide_bottom_panel();
+ tiles_editor_button->hide();
+ //get_tree()->disconnect_compat("idle_frame", tileset_editor, "_on_workspace_process");
+ }
+}
+
+void TilesEditorPlugin::edit(Object *p_object) {
+ tiles_editor->edit(p_object);
+}
+
+bool TilesEditorPlugin::handles(Object *p_object) const {
+ return p_object->is_class("TileMap") || p_object->is_class("TileSet");
+}
+
+TilesEditorPlugin::TilesEditorPlugin(EditorNode *p_node) {
+ editor_node = p_node;
+
+ tiles_editor = memnew(TilesEditor(p_node));
+ tiles_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE);
+ tiles_editor->hide();
+
+ tiles_editor_button = p_node->add_bottom_panel_item(TTR("Tiles"), tiles_editor);
+ tiles_editor_button->hide();
+}
+
+TilesEditorPlugin::~TilesEditorPlugin() {
+}
diff --git a/editor/plugins/tiles/tiles_editor_plugin.h b/editor/plugins/tiles/tiles_editor_plugin.h
new file mode 100644
index 0000000000..6cc6f51598
--- /dev/null
+++ b/editor/plugins/tiles/tiles_editor_plugin.h
@@ -0,0 +1,114 @@
+/*************************************************************************/
+/* tiles_editor_plugin.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 TILES_EDITOR_PLUGIN_H
+#define TILES_EDITOR_PLUGIN_H
+
+#include "editor/editor_plugin.h"
+#include "scene/gui/box_container.h"
+
+#include "tile_atlas_view.h"
+#include "tile_map_editor.h"
+#include "tile_set_editor.h"
+
+class TilesEditor : public VBoxContainer {
+ GDCLASS(TilesEditor, VBoxContainer);
+
+ static TilesEditor *singleton;
+
+private:
+ bool tile_map_changed_needs_update = false;
+ ObjectID tile_map_id;
+ Ref<TileSet> tile_set;
+
+ Button *tileset_tilemap_switch_button;
+
+ Control *tilemap_toolbar;
+ TileMapEditor *tilemap_editor;
+
+ TileSetEditor *tileset_editor;
+
+ void _update_switch_button();
+ void _update_editors();
+
+ // For synchronization.
+ int atlas_sources_lists_current = 0;
+ float atlas_view_zoom = 1.0;
+ Vector2 atlas_view_scroll = Vector2();
+
+ void _tile_map_changed();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ _FORCE_INLINE_ static TilesEditor *get_singleton() { return singleton; }
+
+ bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) { return tilemap_editor->forward_canvas_gui_input(p_event); }
+ void forward_canvas_draw_over_viewport(Control *p_overlay) { tilemap_editor->forward_canvas_draw_over_viewport(p_overlay); }
+
+ // To synchronize the atlas sources lists.
+ void set_atlas_sources_lists_current(int p_current);
+ void synchronize_atlas_sources_list(Object *p_current);
+
+ void set_atlas_view_transform(float p_zoom, Vector2 p_scroll);
+ void synchronize_atlas_view(Object *p_current);
+
+ void edit(Object *p_object);
+
+ TilesEditor(EditorNode *p_editor);
+ ~TilesEditor();
+};
+
+class TilesEditorPlugin : public EditorPlugin {
+ GDCLASS(TilesEditorPlugin, EditorPlugin);
+
+private:
+ EditorNode *editor_node;
+ TilesEditor *tiles_editor;
+ Button *tiles_editor_button;
+
+protected:
+ void _notification(int p_what);
+
+public:
+ virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return tiles_editor->forward_canvas_gui_input(p_event); }
+ virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { tiles_editor->forward_canvas_draw_over_viewport(p_overlay); }
+
+ virtual void edit(Object *p_object) override;
+ virtual bool handles(Object *p_object) const override;
+ virtual void make_visible(bool p_visible) override;
+
+ TilesEditorPlugin(EditorNode *p_node);
+ ~TilesEditorPlugin();
+};
+
+#endif // TILES_EDITOR_PLUGIN_H
diff --git a/icon_outlined.png b/icon_outlined.png
new file mode 100644
index 0000000000..5f1440f287
--- /dev/null
+++ b/icon_outlined.png
Binary files differ
diff --git a/icon_outlined.svg b/icon_outlined.svg
new file mode 100644
index 0000000000..08f9f6f863
--- /dev/null
+++ b/icon_outlined.svg
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="1024"
+ height="1024"
+ id="svg3030"
+ version="1.1"
+ inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"
+ sodipodi:docname="icon_outlined.svg"
+ inkscape:export-filename="/home/riteo/srg/godot-riteo/icon_outlined.png"
+ inkscape:export-xdpi="24"
+ inkscape:export-ydpi="24">
+ <defs
+ id="defs3032" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.8359375"
+ inkscape:cx="512"
+ inkscape:cy="512"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1916"
+ inkscape:window-height="1025"
+ inkscape:window-x="1360"
+ inkscape:window-y="53"
+ inkscape:window-maximized="1"
+ inkscape:document-rotation="0"
+ inkscape:pagecheckerboard="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:object-paths="true"
+ showguides="false" />
+ <metadata
+ id="metadata3035">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-98.519719)">
+ <g
+ id="g78-3-5"
+ transform="matrix(4.1626099,0,0,-4.1626099,919.24188,771.67092)"
+ style="fill:none;fill-opacity:0.527344;stroke:#ffffff;stroke-width:15.58077612;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal">
+ <path
+ id="path80-6-3"
+ style="fill:none;fill-opacity:0.527344;fill-rule:nonzero;stroke:#ffffff;stroke-width:23.49692134;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
+ d="m 226.33398,222.09961 c -15.35819,3.41427 -30.55118,8.16852 -44.79492,15.33789 0.32575,12.57731 1.13879,24.62813 2.78711,36.86914 -5.5316,3.54397 -11.34506,6.58567 -16.51172,10.73438 -5.24959,4.03861 -10.60984,7.90172 -15.36328,12.625 -9.49632,-6.28112 -19.54792,-12.18415 -29.90234,-17.39454 -11.16124,12.01179 -21.59792,24.97673 -30.123049,39.48438 6.699845,10.51682 13.887809,21.08874 20.542969,29.33203 0.0561,36.95701 -0.082,77.98955 0,113.01958 0,51.83844 65.75967,76.75448 147.46094,77.04102 h 0.0996 0.0996 c 81.70127,-0.28654 147.43945,-25.20258 147.43945,-77.04102 0.0505,-37.842 0.0165,-79.50966 0.0214,-113.01953 7.68652,-9.6765 14.96452,-20.35178 20.54102,-29.33203 -8.52211,-14.50765 -18.96376,-27.47259 -30.125,-39.48438 -10.3514,5.21039 -20.40602,11.11342 -29.90234,17.39454 -4.75194,-4.72328 -10.1042,-8.58639 -15.36133,-12.625 -5.16515,-4.14871 -10.98481,-7.19041 -16.50586,-10.73438 1.6438,-12.24101 2.45595,-24.29183 2.7832,-36.86914 -14.24524,-7.16937 -29.43699,-11.92362 -44.80273,-15.33789 -6.13484,10.31068 -11.74471,21.47716 -16.63086,32.39258 -5.79401,-0.96818 -11.61467,-1.32712 -17.44336,-1.39649 v -0.01 c -0.0407,0 -0.0786,0.01 -0.11328,0.01 -0.0362,0 -0.0732,-0.01 -0.10938,-0.01 v 0.01 c -5.83925,0.0694 -11.65565,0.42831 -17.45117,1.39649 -4.88313,-10.91542 -10.49088,-22.0819 -16.63477,-32.39258 z"
+ transform="matrix(0.66309862,0,0,-0.66309862,-270.58935,290.52015)"
+ sodipodi:nodetypes="ccccccccccccccccccccccccsccccc" />
+ </g>
+ <g
+ id="g78"
+ transform="matrix(4.162611,0,0,-4.162611,919.24059,771.67186)"
+ style="stroke-width:0.32031175">
+ <path
+ d="m 0,0 c 0,0 -0.325,1.994 -0.515,1.976 l -36.182,-3.491 c -2.879,-0.278 -5.115,-2.574 -5.317,-5.459 l -0.994,-14.247 -27.992,-1.997 -1.904,12.912 c -0.424,2.872 -2.932,5.037 -5.835,5.037 h -38.188 c -2.902,0 -5.41,-2.165 -5.834,-5.037 l -1.905,-12.912 -27.992,1.997 -0.994,14.247 c -0.202,2.886 -2.438,5.182 -5.317,5.46 l -36.2,3.49 c -0.187,0.018 -0.324,-1.978 -0.511,-1.978 l -0.049,-7.83 30.658,-4.944 1.004,-14.374 c 0.203,-2.91 2.551,-5.263 5.463,-5.472 l 38.551,-2.75 c 0.146,-0.01 0.29,-0.016 0.434,-0.016 2.897,0 5.401,2.166 5.825,5.038 l 1.959,13.286 h 28.005 l 1.959,-13.286 c 0.423,-2.871 2.93,-5.037 5.831,-5.037 0.142,0 0.284,0.005 0.423,0.015 l 38.556,2.75 c 2.911,0.209 5.26,2.562 5.463,5.472 l 1.003,14.374 30.645,4.966 z"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32031175"
+ id="path80"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ id="g82-3"
+ transform="matrix(4.162611,0,0,-4.162611,104.69892,525.90697)"
+ style="stroke-width:0.32031175">
+ <path
+ d="m 0,0 v -47.514 -6.035 -5.492 c 0.108,-0.001 0.216,-0.005 0.323,-0.015 l 36.196,-3.49 c 1.896,-0.183 3.382,-1.709 3.514,-3.609 l 1.116,-15.978 31.574,-2.253 2.175,14.747 c 0.282,1.912 1.922,3.329 3.856,3.329 h 38.188 c 1.933,0 3.573,-1.417 3.855,-3.329 l 2.175,-14.747 31.575,2.253 1.115,15.978 c 0.133,1.9 1.618,3.425 3.514,3.609 l 36.182,3.49 c 0.107,0.01 0.214,0.014 0.322,0.015 v 4.711 l 0.015,0.005 V 0 c 5.09692,6.4164715 9.92323,13.494208 13.621,19.449 -5.651,9.62 -12.575,18.217 -19.976,26.182 -6.864,-3.455 -13.531,-7.369 -19.828,-11.534 -3.151,3.132 -6.7,5.694 -10.186,8.372 -3.425,2.751 -7.285,4.768 -10.946,7.118 1.09,8.117 1.629,16.108 1.846,24.448 -9.446,4.754 -19.519,7.906 -29.708,10.17 -4.068,-6.837 -7.788,-14.241 -11.028,-21.479 -3.842,0.642 -7.702,0.88 -11.567,0.926 v 0.006 c -0.027,0 -0.052,-0.006 -0.075,-0.006 -0.024,0 -0.049,0.006 -0.073,0.006 V 63.652 C 93.903,63.606 90.046,63.368 86.203,62.726 82.965,69.964 79.247,77.368 75.173,84.205 64.989,81.941 54.915,78.789 45.47,74.035 45.686,65.695 46.225,57.704 47.318,49.587 43.65,47.237 39.795,45.22 36.369,42.469 32.888,39.791 29.333,37.229 26.181,34.097 19.884,38.262 13.219,42.176 6.353,45.631 -1.048,37.666 -7.968,29.069 -13.621,19.449 -9.1783421,12.475308 -4.4130298,5.4661124 0,0 Z"
+ style="fill:#478cbf;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.320312"
+ id="path84-6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccsssscccccccccccccccccccsccccccccccc" />
+ </g>
+ <g
+ id="g86-7"
+ transform="matrix(4.162611,0,0,-4.162611,784.07144,817.24284)"
+ style="stroke-width:0.32031175">
+ <path
+ d="m 0,0 -1.121,-16.063 c -0.135,-1.936 -1.675,-3.477 -3.611,-3.616 l -38.555,-2.751 c -0.094,-0.007 -0.188,-0.01 -0.281,-0.01 -1.916,0 -3.569,1.406 -3.852,3.33 l -2.211,14.994 H -81.09 l -2.211,-14.994 c -0.297,-2.018 -2.101,-3.469 -4.133,-3.32 l -38.555,2.751 c -1.936,0.139 -3.476,1.68 -3.611,3.616 L -130.721,0 -163.268,3.138 c 0.015,-3.498 0.06,-7.33 0.06,-8.093 0,-34.374 43.605,-50.896 97.781,-51.086 h 0.066 0.067 c 54.176,0.19 97.766,16.712 97.766,51.086 0,0.777 0.047,4.593 0.063,8.093 z"
+ style="fill:#478cbf;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32031175"
+ id="path88-5"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ id="g90-3"
+ transform="matrix(4.162611,0,0,-4.162611,389.21484,625.67104)"
+ style="stroke-width:0.32031175">
+ <path
+ d="m 0,0 c 0,-12.052 -9.765,-21.815 -21.813,-21.815 -12.042,0 -21.81,9.763 -21.81,21.815 0,12.044 9.768,21.802 21.81,21.802 C -9.765,21.802 0,12.044 0,0"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32031175"
+ id="path92-5"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ id="g94-6"
+ transform="matrix(4.162611,0,0,-4.162611,367.36686,631.05679)"
+ style="stroke-width:0.32031175">
+ <path
+ d="m 0,0 c 0,-7.994 -6.479,-14.473 -14.479,-14.473 -7.996,0 -14.479,6.479 -14.479,14.473 0,7.994 6.483,14.479 14.479,14.479 C -6.479,14.479 0,7.994 0,0"
+ style="fill:#414042;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32031175"
+ id="path96-2"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ id="g98-9"
+ transform="matrix(4.162611,0,0,-4.162611,511.99336,724.73954)"
+ style="stroke-width:0.32031175">
+ <path
+ d="m 0,0 c -3.878,0 -7.021,2.858 -7.021,6.381 v 20.081 c 0,3.52 3.143,6.381 7.021,6.381 3.878,0 7.028,-2.861 7.028,-6.381 V 6.381 C 7.028,2.858 3.878,0 0,0"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32031175"
+ id="path100-1"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ id="g102-2"
+ transform="matrix(4.162611,0,0,-4.162611,634.78706,625.67104)"
+ style="stroke-width:0.32031175">
+ <path
+ d="m 0,0 c 0,-12.052 9.765,-21.815 21.815,-21.815 12.041,0 21.808,9.763 21.808,21.815 0,12.044 -9.767,21.802 -21.808,21.802 C 9.765,21.802 0,12.044 0,0"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32031175"
+ id="path104-7"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ id="g106-0"
+ transform="matrix(4.162611,0,0,-4.162611,656.64056,631.05679)"
+ style="stroke-width:0.32031175">
+ <path
+ d="m 0,0 c 0,-7.994 6.477,-14.473 14.471,-14.473 8.002,0 14.479,6.479 14.479,14.473 0,7.994 -6.477,14.479 -14.479,14.479 C 6.477,14.479 0,7.994 0,0"
+ style="fill:#414042;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32031175"
+ id="path108-9"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ id="g78-3"
+ transform="matrix(4.1626558,0,0,-4.1626558,918.83083,771.66929)"
+ style="fill:#000000;fill-opacity:0.527344;stroke:none;stroke-width:10.0248;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/logo_outlined.png b/logo_outlined.png
new file mode 100644
index 0000000000..44b85cde57
--- /dev/null
+++ b/logo_outlined.png
Binary files differ
diff --git a/logo_outlined.svg b/logo_outlined.svg
new file mode 100644
index 0000000000..4079a1dd56
--- /dev/null
+++ b/logo_outlined.svg
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ id="svg2"
+ version="1.1"
+ inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"
+ xml:space="preserve"
+ width="1024"
+ height="414"
+ viewBox="0 0 960.00002 388.12499"
+ sodipodi:docname="logo_outlined.svg"
+ inkscape:export-filename="/home/riteo/srg/godot-riteo/logo_outlined.png"
+ inkscape:export-xdpi="48"
+ inkscape:export-ydpi="48"><metadata
+ id="metadata8"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
+ id="defs6"><clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath16"><path
+ d="M 0,595.276 H 841.89 V 0 H 0 Z"
+ id="path18"
+ inkscape:connector-curvature="0" /></clipPath></defs><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1916"
+ inkscape:window-height="1025"
+ id="namedview4"
+ showgrid="false"
+ inkscape:zoom="1.2041016"
+ inkscape:cx="512"
+ inkscape:cy="207"
+ inkscape:window-x="1360"
+ inkscape:window-y="53"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="g14"
+ fit-margin-top="48"
+ fit-margin-left="48"
+ fit-margin-right="48"
+ fit-margin-bottom="48"
+ inkscape:document-rotation="0"
+ inkscape:pagecheckerboard="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:object-paths="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-bbox="false"
+ inkscape:bbox-paths="false"
+ inkscape:snap-bbox-edge-midpoints="false"
+ inkscape:snap-bbox-midpoints="false"
+ inkscape:object-nodes="true"
+ inkscape:snap-others="false"
+ units="px" /><g
+ id="g10"
+ inkscape:groupmode="layer"
+ inkscape:label="godot_engine_logo_2017_curves-01"
+ transform="matrix(1.25,0,0,-1.25,-94.249997,597.49874)"><g
+ id="g12"><g
+ id="g14"
+ clip-path="url(#clipPath16)"><g
+ id="g78-3"
+ transform="matrix(1.1310535,0,0,1.1310535,348.13118,279.26721)"
+ style="fill:none;stroke:#ffffff;stroke-width:15.5885606;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"><path
+ id="path80-6"
+ style="fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:23.50866089;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 226.33398,222.09961 c -15.35819,3.41427 -30.55118,8.16852 -44.79492,15.33789 0.32575,12.57731 1.13879,24.62813 2.78711,36.86914 -5.5316,3.54397 -11.34506,6.58567 -16.51172,10.73438 -5.24959,4.03861 -10.60984,7.90172 -15.36328,12.625 -9.49632,-6.28112 -19.54792,-12.18415 -29.90234,-17.39454 -11.16124,12.01179 -21.59792,24.97673 -30.123049,39.48438 6.699845,10.51682 13.887809,21.08874 20.542969,29.33203 0.0561,36.95701 -0.082,77.98955 0,113.01958 0,51.83844 65.75967,76.75448 147.46094,77.04102 h 0.0996 0.0996 c 81.70127,-0.28654 147.43945,-25.20258 147.43945,-77.04102 0.0505,-37.842 0.0165,-79.50966 0.0214,-113.01953 7.68652,-9.6765 14.96452,-20.35178 20.54102,-29.33203 -8.52211,-14.50765 -18.96376,-27.47259 -30.125,-39.48438 -10.3514,5.21039 -20.40602,11.11342 -29.90234,17.39454 -4.75194,-4.72328 -10.1042,-8.58639 -15.36133,-12.625 -5.16515,-4.14871 -10.98481,-7.19041 -16.50586,-10.73438 1.6438,-12.24101 2.45595,-24.29183 2.7832,-36.86914 -14.24524,-7.16937 -29.43699,-11.92362 -44.80273,-15.33789 -6.13484,10.31068 -11.74471,21.47716 -16.63086,32.39258 -5.79401,-0.96818 -11.61467,-1.32712 -17.44336,-1.39649 v -0.01 c -0.0407,0 -0.0786,0.01 -0.11328,0.01 -0.0362,0 -0.0732,-0.01 -0.10938,-0.01 v 0.01 c -5.83925,0.0694 -11.65565,0.42831 -17.45117,1.39649 -4.88313,-10.91542 -10.49088,-22.0819 -16.63477,-32.39258 z"
+ transform="matrix(0.66309862,0,0,-0.66309862,-270.58935,290.52015)"
+ sodipodi:nodetypes="ccccccccccccccccccccccccsccccc" /></g><g
+ id="g20"
+ transform="matrix(1.1310535,0,0,1.1310535,531.44953,355.31567)"
+ style="stroke:none;stroke-width:10.60874228;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" /><g
+ id="g24"
+ transform="matrix(1.1310535,0,0,1.1310535,607.8515,354.43097)"
+ style="stroke:none;stroke-width:10.60874228;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" /><g
+ id="g28"
+ transform="matrix(1.1310535,0,0,1.1310535,700.81066,355.31567)"
+ style="stroke:none;stroke-width:10.60874228;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" /><g
+ id="g32"
+ transform="matrix(1.1310535,0,0,1.1310535,789.01132,291.33514)"
+ style="stroke:none;stroke-width:10.60874228;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" /><g
+ id="g36"
+ transform="matrix(1.1310535,0,0,1.1310535,468.26549,336.71278)"
+ style="stroke:none;stroke-width:10.60874228;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"><path
+ id="path38"
+ style="fill:#414042;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:15.5885606;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
+ d="m 0,0 c -6.078,0.094 -13.034,-1.173 -13.034,-1.173 v -11.863 h 6.995 l -0.078,-5.288 c 0,-1.959 -1.942,-2.943 -5.815,-2.943 -3.878,0 -7.303,1.642 -10.274,4.917 -2.978,3.279 -4.459,8.072 -4.459,14.388 0,6.329 1.447,10.995 4.345,14.006 2.892,3.008 6.683,4.517 11.346,4.517 1.959,0 3.987,-0.316 6.096,-0.961 2.11,-0.639 3.519,-1.238 4.238,-1.799 0.713,-0.577 1.391,-0.85 2.032,-0.85 0.638,0 1.671,0.746 3.1,2.255 1.431,1.505 2.713,3.786 3.844,6.827 1.126,3.057 1.69,5.4 1.69,7.062 0,1.649 -0.036,2.786 -0.109,3.386 -1.581,1.73 -4.499,3.102 -8.755,4.122 -4.248,1.017 -9.011,1.522 -14.28,1.522 -11.594,0 -20.66,-3.65 -27.207,-10.95 -6.552,-7.303 -9.822,-16.783 -9.822,-28.452 0,-13.701 3.347,-24.087 10.041,-31.162 6.706,-7.074 15.51,-10.607 26.425,-10.607 5.87,0 11.08,0.505 15.632,1.522 4.557,1.013 7.586,2.053 9.093,3.105 l 0.452,35.33 C 11.496,-1.036 6.078,-0.104 0,0 m 283.58148,-40.1198 c 0,-1.496 -3.721,-2.255 -11.176,-2.255 -7.448,0 -11.18,0.759 -11.18,2.255 v 56.681 h -13.545 c -1.281,0 -2.185,1.727 -2.71,5.198 -0.226,1.652 -0.334,3.343 -0.334,5.077 0,1.724 0.108,3.422 0.334,5.077 0.525,3.462 1.429,5.202 2.71,5.202 h 49.112 c 1.279,0 2.179,-1.74 2.712,-5.202 0.221,-1.655 0.335,-3.353 0.335,-5.077 0,-1.734 -0.114,-3.425 -0.335,-5.077 -0.533,-3.471 -1.433,-5.198 -2.712,-5.198 h -13.211 z M 205.6005,16.447401 c -3.612,0 -6.645,-1.659 -9.095,-4.967 -2.44,-3.3110004 -3.662,-7.9580004 -3.662,-13.9380004 0,-5.993 1.169,-10.5809996 3.499,-13.7779996 2.33,-3.207 5.398,-4.804 9.2,-4.804 3.801,0 6.89,1.617 9.258,4.862 2.372,3.233 3.56,7.8609996 3.56,13.8859996 0,6.02 -1.225,10.654 -3.671,13.8900004 -2.447,3.232 -5.473,4.849 -9.089,4.849 m -0.058,-59.493 c -10.577,0 -19.193,3.46 -25.851,10.379 -6.663,6.925 -9.993,17.03 -9.993,30.3139996 0,13.2920004 3.367,23.3560004 10.1,30.2090004 6.741,6.844 15.431,10.269 26.086,10.269 10.651,0 19.246,-3.363 25.797,-10.109 6.55,-6.733 9.822,-16.94 9.822,-30.5910004 0,-13.6609996 -3.349,-23.8219996 -10.05,-30.4899996 -6.699,-6.654 -15.338,-9.981 -25.911,-9.981 m -82.13011,58.710808 v -33.768 c 0,-1.577 0.116,-2.571 0.342,-2.988 0.224,-0.415 0.903,-0.623 2.029,-0.623 4.144,0 7.283,1.548 9.429,4.634 2.151,3.083 3.215,8.2160005 3.215,15.4050005 0,7.192 -1.113,11.8779995 -3.325,14.0549995 -2.223,2.183 -5.744,3.285 -10.561,3.285 z m -21.675,-52.392 v 67.735 c 0,1.883 0.468,3.369 1.413,4.471 0.939,1.085 2.161,1.636 3.671,1.636 h 18.854 c 11.965,0 21.053,-3.018 27.257,-9.04 6.215,-6.02 9.322,-15.499 9.322,-28.44699953 0,-27.70000047 -11.821,-41.54700047 -35.456,-41.54700047 h -19.302 c -3.836,0 -5.759,1.727 -5.759,5.192 M 55.862999,16.447401 c -3.611,0 -6.636,-1.659 -9.09,-4.967 -2.441,-3.3110004 -3.668,-7.9580004 -3.668,-13.9380004 0,-5.993 1.166,-10.5809996 3.503,-13.7779996 2.333,-3.207 5.398,-4.804 9.2,-4.804 3.8,0 6.887,1.617 9.258,4.862 2.371,3.233 3.559,7.8609996 3.559,13.8859996 0,6.02 -1.227,10.654 -3.673,13.8900004 -2.443,3.232 -5.473,4.849 -9.089,4.849 m -0.055,-59.493 c -10.573,0 -19.195,3.46 -25.859,10.379 -6.655,6.925 -9.984,17.03 -9.984,30.3139996 0,13.2920004 3.367,23.3560004 10.101,30.2090004 6.736,6.844 15.431,10.269 26.082,10.269 10.649,0 19.251,-3.363 25.794,-10.109 6.555,-6.733 9.827,-16.94 9.827,-30.5910004 0,-13.6609996 -3.348,-23.8219996 -10.05,-30.4899996 -6.702,-6.654 -15.333,-9.981 -25.911,-9.981" /></g><g
+ id="g40"
+ transform="matrix(1.1310535,0,0,1.1310535,441.34721,235.75121)"
+ style="stroke:none;stroke-width:4.31066306;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" /><g
+ id="g44"
+ transform="matrix(1.1310535,0,0,1.1310535,456.01527,232.82495)"
+ style="stroke:none;stroke-width:4.31066306;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" /><g
+ id="g48"
+ transform="matrix(1.1310535,0,0,1.1310535,476.7303,259.10521)"
+ style="stroke:none;stroke-width:4.31066306;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" /><g
+ id="g52"
+ transform="matrix(1.1310535,0,0,1.1310535,522.82277,256.83868)"
+ style="stroke:none;stroke-width:4.31066306;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" /><g
+ id="g56"
+ transform="matrix(1.1310535,0,0,1.1310535,558.0805,256.83868)"
+ style="stroke:none;stroke-width:4.31066306;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" /><g
+ id="g60"
+ transform="matrix(1.1310535,0,0,1.1310535,575.91679,259.10521)"
+ style="stroke:none;stroke-width:4.31066306;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" /><g
+ id="g64"
+ transform="matrix(1.1310535,0,0,1.1310535,600.8685,242.30884)"
+ style="stroke:none;stroke-width:4.31066306;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" /><g
+ id="g70"
+ transform="matrix(1.1310535,0,0,1.1310535,638.15379,259.10521)"
+ style="stroke:none;stroke-width:4.31066306;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" /><g
+ id="g74"
+ transform="matrix(1.1310535,0,0,1.1310535,669.70883,256.83868)"
+ style="stroke:none;stroke-width:4.31066306;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"><path
+ id="path76"
+ style="fill:#6d6e71;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:15.5885606;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
+ d="M 0,0 C -1.763,0 -3.208,-0.802 -4.334,-2.402 -5.463,-4.008 -6.052,-5.987 -6.102,-8.346 H 5.56 v 1.082 c 0,2.086 -0.486,3.823 -1.47,5.201 C 3.109,-0.684 1.747,0 0,0 m 0.401,-23.76 c -2.733,0 -4.958,1.026 -6.681,3.073 -1.73,2.043 -2.595,4.657 -2.595,7.841 v 4.197 c 0,3.19 0.865,5.85 2.6,7.965 1.739,2.105 3.831,3.158 6.275,3.158 2.646,0 4.706,-0.939 6.172,-2.823 1.462,-1.887 2.195,-4.422 2.195,-7.603 v -2.773 H -6.102 v -2.102 c 0,-2.447 0.59,-4.484 1.757,-6.11 1.166,-1.63 2.748,-2.438 4.746,-2.438 1.382,0 2.579,0.244 3.578,0.724 1.012,0.491 1.869,1.179 2.591,2.082 l 1.147,-1.988 c -0.769,-0.968 -1.755,-1.75 -2.962,-2.33 -1.203,-0.577 -2.658,-0.873 -4.354,-0.873 m -28.299804,25.7639105 0.23,-4.178 c 0.676,1.48300001 1.562,2.63400001 2.678,3.435 1.115,0.805 2.422,1.213 3.916,1.213 2.258,0 3.995,-0.835 5.199,-2.51299999 1.211,-1.67500001 1.807,-4.27900001 1.807,-7.81200001 V -23.271089 h -2.825 v 15.3939995 c 0,2.888 -0.422,4.905 -1.261,6.075 -0.843,1.17000001 -2.063,1.75300001 -3.668,1.75300001 -1.434,0 -2.635,-0.466 -3.599,-1.41400001 -0.967,-0.939 -1.692,-2.19 -2.171,-3.767 V -23.271089 h -2.809 V 2.0039105 Z m -9.133591,-25.2752055 h -2.830008 V 2.0037072 h 2.830008 z m 0,32.4710013 h -2.830008 v 3.9819957 h 2.830008 z M -60.863903,-12.846289 c 0,-2.565 0.486,-4.605 1.472,-6.123 0.974,-1.532 2.457,-2.288 4.436,-2.288 1.356,0 2.498,0.361 3.435,1.101 0.934,0.74 1.672,1.77 2.218,3.077 v 12.5200001 c -0.525,1.346 -1.246,2.434 -2.157,3.272 -0.91,0.82400002 -2.062,1.23800002 -3.448,1.23800002 -1.975,0 -3.468,-0.86 -4.46,-2.58700002 -0.999,-1.73 -1.496,-3.986 -1.496,-6.756 z m -2.833,3.4540001 c 0,3.582 0.723,6.459 2.177,8.62700002 1.442,2.15699998 3.448,3.23899998 6.004,3.23899998 1.419,0 2.664,-0.346 3.728,-1.04 1.066,-0.68099998 1.947,-1.67799998 2.654,-2.946 l 0.274,3.516 h 2.381 V -23.294289 c 0,-3.239 -0.751,-5.749 -2.26,-7.525 -1.511,-1.769 -3.657,-2.665 -6.428,-2.665 -0.996,0 -2.067,0.156 -3.212,0.459 -1.147,0.303 -2.162,0.701 -3.052,1.2 l 0.776,2.463 c 0.759,-0.492 1.608,-0.873 2.548,-1.141 0.932,-0.277 1.895,-0.41 2.894,-0.41 2.009,0 3.498,0.645 4.46,1.932 0.966,1.304 1.45,3.19 1.45,5.687 v 3.057 c -0.717,-1.138 -1.597,-2.011 -2.64,-2.614 -1.039,-0.606 -2.253,-0.909 -3.622,-0.909 -2.539,0 -4.53,0.994 -5.968,2.982 -1.441,1.984 -2.164,4.631 -2.164,7.932 z m -19.227592,11.3961994 0.23,-4.178 c 0.674,1.48300001 1.564,2.63400001 2.682,3.435 1.108,0.805 2.413,1.213 3.914,1.213 2.258,0 3.988,-0.835 5.189,-2.51299999 1.214,-1.67500001 1.815,-4.27900001 1.815,-7.81200001 V -23.271089 h -2.825 v 15.3939995 c 0,2.888 -0.423,4.905 -1.264,6.075 -0.836,1.17000001 -2.065,1.75300001 -3.665,1.75300001 -1.435,0 -2.638,-0.466 -3.603,-1.41400001 -0.969,-0.939 -1.691,-2.19 -2.172,-3.767 V -23.271089 h -2.805 V 2.0039105 Z M -98.69412,0 c -1.763,0 -3.21,-0.802 -4.341,-2.402 -1.126,-1.606 -1.712,-3.585 -1.763,-5.944 h 11.663 v 1.082 c 0,2.086 -0.488,3.823 -1.474,5.201 -0.981,1.379 -2.341,2.063 -4.085,2.063 m 0.394,-23.76 c -2.726,0 -4.951,1.026 -6.679,3.073 -1.733,2.043 -2.6,4.657 -2.6,7.841 v 4.197 c 0,3.19 0.871,5.85 2.602,7.965 1.744,2.105 3.834,3.158 6.283,3.158 2.643,0 4.703,-0.939 6.164,-2.823 1.463,-1.887 2.197,-4.422 2.197,-7.603 v -2.773 h -14.465 v -2.102 c 0,-2.447 0.587,-4.484 1.76,-6.11 1.162,-1.63 2.742,-2.438 4.738,-2.438 1.387,0 2.585,0.244 3.585,0.724 1.007,0.491 1.866,1.179 2.589,2.082 l 1.141,-1.988 c -0.764,-0.968 -1.75,-1.75 -2.959,-2.33 -1.204,-0.577 -2.658,-0.873 -4.356,-0.873 M -129.86659,0 c -1.758,0 -3.202,-0.802 -4.334,-2.402 -1.133,-1.606 -1.718,-3.585 -1.765,-5.944 h 11.66 v 1.082 c 0,2.086 -0.489,3.823 -1.469,5.201 -0.986,1.379 -2.347,2.063 -4.092,2.063 m 0.397,-23.76 c -2.725,0 -4.954,1.026 -6.685,3.073 -1.726,2.043 -2.591,4.657 -2.591,7.841 v 4.197 c 0,3.19 0.867,5.85 2.602,7.965 1.739,2.105 3.828,3.158 6.277,3.158 2.648,0 4.699,-0.939 6.164,-2.823 1.468,-1.887 2.201,-4.422 2.201,-7.603 v -2.773 h -14.464 v -2.102 c 0,-2.447 0.586,-4.484 1.752,-6.11 1.168,-1.63 2.755,-2.438 4.744,-2.438 1.382,0 2.585,0.244 3.588,0.724 1.003,0.491 1.863,1.179 2.578,2.082 l 1.149,-1.988 c -0.763,-0.968 -1.752,-1.75 -2.959,-2.33 -1.204,-0.577 -2.659,-0.873 -4.356,-0.873 m -41.1488,25.7639105 0.24,-3.923 c 0.664,1.40400001 1.554,2.48600001 2.657,3.255 1.107,0.759 2.41,1.138 3.906,1.138 1.527,0 2.814,-0.444 3.852,-1.343 1.039,-0.89599999 1.805,-2.252 2.292,-4.074 0.623,1.682 1.505,3.01100001 2.65,3.973 1.145,0.964 2.534,1.444 4.143,1.444 2.217,0 3.937,-0.897 5.156,-2.69199999 1.224,-1.79900001 1.834,-4.55900001 1.834,-8.28800001 V -23.271089 h -2.823 v 14.8139995 c 0,3.1 -0.429,5.283 -1.263,6.538 -0.839,1.25700001 -2.042,1.89000001 -3.598,1.89000001 -1.637,0 -2.915,-0.691 -3.834,-2.09600001 -0.914,-1.405 -1.478,-3.161 -1.683,-5.282 v -0.655 -15.2089995 h -2.809 v 14.7979995 c 0,3.027 -0.424,5.194 -1.292,6.488 -0.864,1.29400001 -2.066,1.93600001 -3.609,1.93600001 -1.475,0 -2.668,-0.45 -3.562,-1.34200001 -0.9,-0.897 -1.54,-2.125 -1.928,-3.683 V -23.271089 h -2.806 V 2.0039105 Z M -188.9332,-21.231295 c 1.553,0 2.936,0.44 4.144,1.336 1.21,0.9 2.058,2.037 2.561,3.422 v 5.468 h -4.492 c -1.91,0 -3.44,-0.541 -4.585,-1.623 -1.148,-1.075 -1.716,-2.418 -1.716,-4.015 0,-1.349 0.355,-2.457 1.074,-3.311 0.718,-0.857 1.722,-1.277 3.014,-1.277 m 7.124,-2.04 c -0.14,0.876 -0.249,1.587 -0.318,2.144 -0.067,0.567 -0.101,1.131 -0.101,1.704 -0.767,-1.254 -1.757,-2.294 -2.98,-3.109 -1.221,-0.821 -2.579,-1.228 -4.075,-1.228 -2.092,0 -3.701,0.648 -4.84,1.946 -1.132,1.303 -1.704,3.059 -1.704,5.276 0,2.343 0.823,4.223 2.473,5.618 1.649,1.3950005 3.89,2.0920005 6.709,2.0920005 h 4.417 v 3.106 c 0,1.786 -0.456,3.193 -1.351,4.21 -0.914,1.00399996 -2.17,1.51199995911 -3.791,1.51199995911 -1.508,0 -2.752,-0.47899999911 -3.728,-1.44999995911 -0.973,-0.965 -1.456,-2.144 -1.456,-3.549 l -2.623,0.023 -0.046,0.137 c -0.074,1.906 0.647,3.591 2.168,5.08399996 1.515,1.48900004 3.459,2.22900004 5.825,2.22900004 2.338,0 4.22,-0.711 5.657,-2.12800004 1.429,-1.43099996 2.146,-3.47099996 2.146,-6.12399996 V -18.174295 c 0,-0.903 0.042,-1.78 0.121,-2.617 0.081,-0.848 0.212,-1.665 0.417,-2.48 z m -20.0925,4.627199 c -0.624,-1.28 -1.771,-2.454 -3.449,-3.516 -1.676,-1.069 -3.805,-1.6 -6.391,-1.6 -3.412,0 -6.156,1.075 -8.24,3.249 -2.076,2.157 -3.116,5.266 -3.116,9.323 v 10.116 c 0,3.969 0.98,7.013 2.946,9.138 1.962,2.108 4.59,3.177 7.872,3.177 3.208,0 5.695,-0.844 7.455,-2.513 1.755,-1.675 2.677,-4.015 2.757,-7.003 l -0.044,-0.133 h -2.619 c -0.094,2.29 -0.759,4.057 -2.01,5.305 -1.244,1.238 -3.095,1.864 -5.539,1.864 -2.473,0 -4.432,-0.837 -5.866,-2.516 -1.43,-1.675 -2.143,-4.103 -2.143,-7.293 v -10.174 c 0,-3.308 0.771,-5.83 2.311,-7.567 1.54,-1.724 3.616,-2.588 6.236,-2.588 1.913,0 3.451,0.339 4.602,1.033 1.155,0.684 1.956,1.519 2.409,2.51 v 8.861 h -7.06 v 2.463 h 9.889 z" /></g><g
+ id="g78"
+ transform="matrix(1.1310535,0,0,1.1310535,348.13109,279.2668)"
+ style="stroke-width:0.884131"><path
+ d="m 0,0 c 0,0 -0.325,1.994 -0.515,1.976 l -36.182,-3.491 c -2.879,-0.278 -5.115,-2.574 -5.317,-5.459 l -0.994,-14.247 -27.992,-1.997 -1.904,12.912 c -0.424,2.872 -2.932,5.037 -5.835,5.037 h -38.188 c -2.902,0 -5.41,-2.165 -5.834,-5.037 l -1.905,-12.912 -27.992,1.997 -0.994,14.247 c -0.202,2.886 -2.438,5.182 -5.317,5.46 l -36.2,3.49 c -0.187,0.018 -0.324,-1.978 -0.511,-1.978 l -0.049,-7.83 30.658,-4.944 1.004,-14.374 c 0.203,-2.91 2.551,-5.263 5.463,-5.472 l 38.551,-2.75 c 0.146,-0.01 0.29,-0.016 0.434,-0.016 2.897,0 5.401,2.166 5.825,5.038 l 1.959,13.286 h 28.005 l 1.959,-13.286 c 0.423,-2.871 2.93,-5.037 5.831,-5.037 0.142,0 0.284,0.005 0.423,0.015 l 38.556,2.75 c 2.911,0.209 5.26,2.562 5.463,5.472 l 1.003,14.374 30.645,4.966 z"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.884131"
+ id="path80"
+ inkscape:connector-curvature="0" /></g><g
+ id="g82"
+ transform="matrix(1.1310535,0,0,1.1310535,126.80608,346.04533)"
+ style="stroke-width:0.884131"><path
+ d="m 0,0 v -47.514 -6.035 -5.492 c 0.108,-0.001 0.216,-0.005 0.323,-0.015 l 36.196,-3.49 c 1.896,-0.183 3.382,-1.709 3.514,-3.609 l 1.116,-15.978 31.574,-2.253 2.175,14.747 c 0.282,1.912 1.922,3.329 3.856,3.329 h 38.188 c 1.933,0 3.573,-1.417 3.855,-3.329 l 2.175,-14.747 31.575,2.253 1.115,15.978 c 0.133,1.9 1.618,3.425 3.514,3.609 l 36.182,3.49 c 0.107,0.01 0.214,0.014 0.322,0.015 v 4.711 l 0.015,0.005 V 0 c 5.09692,6.4164715 9.92323,13.494208 13.621,19.449 -5.651,9.62 -12.575,18.217 -19.976,26.182 -6.864,-3.455 -13.531,-7.369 -19.828,-11.534 -3.151,3.132 -6.7,5.694 -10.186,8.372 -3.425,2.751 -7.285,4.768 -10.946,7.118 1.09,8.117 1.629,16.108 1.846,24.448 -9.446,4.754 -19.519,7.906 -29.708,10.17 -4.068,-6.837 -7.788,-14.241 -11.028,-21.479 -3.842,0.642 -7.702,0.88 -11.567,0.926 v 0.006 c -0.027,0 -0.052,-0.006 -0.075,-0.006 -0.024,0 -0.049,0.006 -0.073,0.006 V 63.652 C 93.903,63.606 90.046,63.368 86.203,62.726 82.965,69.964 79.247,77.368 75.173,84.205 64.989,81.941 54.915,78.789 45.47,74.035 45.686,65.695 46.225,57.704 47.318,49.587 43.65,47.237 39.795,45.22 36.369,42.469 32.888,39.791 29.333,37.229 26.181,34.097 19.884,38.262 13.219,42.176 6.353,45.631 -1.048,37.666 -7.968,29.069 -13.621,19.449 -9.1783421,12.475308 -4.4130298,5.4661124 0,0 Z"
+ style="fill:#478cbf;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.884131"
+ id="path84"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccsssscccccccccccccccccccsccccccccccc" /></g><g
+ id="g86"
+ transform="matrix(1.1310535,0,0,1.1310535,311.40329,266.88437)"
+ style="stroke-width:0.884131"><path
+ d="m 0,0 -1.121,-16.063 c -0.135,-1.936 -1.675,-3.477 -3.611,-3.616 l -38.555,-2.751 c -0.094,-0.007 -0.188,-0.01 -0.281,-0.01 -1.916,0 -3.569,1.406 -3.852,3.33 l -2.211,14.994 H -81.09 l -2.211,-14.994 c -0.297,-2.018 -2.101,-3.469 -4.133,-3.32 l -38.555,2.751 c -1.936,0.139 -3.476,1.68 -3.611,3.616 L -130.721,0 -163.268,3.138 c 0.015,-3.498 0.06,-7.33 0.06,-8.093 0,-34.374 43.605,-50.896 97.781,-51.086 h 0.066 0.067 c 54.176,0.19 97.766,16.712 97.766,51.086 0,0.777 0.047,4.593 0.063,8.093 z"
+ style="fill:#478cbf;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.884131"
+ id="path88"
+ inkscape:connector-curvature="0" /></g><g
+ id="g90"
+ transform="matrix(1.1310535,0,0,1.1310535,204.11393,318.93771)"
+ style="stroke-width:0.884131"><path
+ d="m 0,0 c 0,-12.052 -9.765,-21.815 -21.813,-21.815 -12.042,0 -21.81,9.763 -21.81,21.815 0,12.044 9.768,21.802 21.81,21.802 C -9.765,21.802 0,12.044 0,0"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.884131"
+ id="path92"
+ inkscape:connector-curvature="0" /></g><g
+ id="g94"
+ transform="matrix(1.1310535,0,0,1.1310535,198.17748,317.47435)"
+ style="stroke-width:0.884131"><path
+ d="m 0,0 c 0,-7.994 -6.479,-14.473 -14.479,-14.473 -7.996,0 -14.479,6.479 -14.479,14.473 0,7.994 6.483,14.479 14.479,14.479 C -6.479,14.479 0,7.994 0,0"
+ style="fill:#414042;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.884131"
+ id="path96"
+ inkscape:connector-curvature="0" /></g><g
+ id="g98"
+ transform="matrix(1.1310535,0,0,1.1310535,237.47503,292.01909)"
+ style="stroke-width:0.884131"><path
+ d="m 0,0 c -3.878,0 -7.021,2.858 -7.021,6.381 v 20.081 c 0,3.52 3.143,6.381 7.021,6.381 3.878,0 7.028,-2.861 7.028,-6.381 V 6.381 C 7.028,2.858 3.878,0 0,0"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.884131"
+ id="path100"
+ inkscape:connector-curvature="0" /></g><g
+ id="g102"
+ transform="matrix(1.1310535,0,0,1.1310535,270.84021,318.93771)"
+ style="stroke-width:0.884131"><path
+ d="m 0,0 c 0,-12.052 9.765,-21.815 21.815,-21.815 12.041,0 21.808,9.763 21.808,21.815 0,12.044 -9.767,21.802 -21.808,21.802 C 9.765,21.802 0,12.044 0,0"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.884131"
+ id="path104"
+ inkscape:connector-curvature="0" /></g><g
+ id="g106"
+ transform="matrix(1.1310535,0,0,1.1310535,276.77813,317.47435)"
+ style="stroke-width:0.884131"><path
+ d="m 0,0 c 0,-7.994 6.477,-14.473 14.471,-14.473 8.002,0 14.479,6.479 14.479,14.473 0,7.994 -6.477,14.479 -14.479,14.479 C 6.477,14.479 0,7.994 0,0"
+ style="fill:#414042;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.884131"
+ id="path108"
+ inkscape:connector-curvature="0" /></g></g></g></g></svg>
diff --git a/misc/hooks/pre-commit-clang-format b/misc/hooks/pre-commit-clang-format
index 7c6e5fcb42..2dac8e9a55 100755
--- a/misc/hooks/pre-commit-clang-format
+++ b/misc/hooks/pre-commit-clang-format
@@ -99,8 +99,12 @@ if [ ! -x "$CLANG_FORMAT" ] ; then
exit 1
fi
-CLANG_FORMAT_VERSION="$(clang-format --version | cut -d' ' -f3)"
-CLANG_FORMAT_MAJOR="$(echo "$CLANG_FORMAT_VERSION" | cut -d'.' -f1)"
+# The returned string can be inconsistent depending on where clang-format comes from.
+# Example output strings reported by `clang-format --version`:
+# - Ubuntu: "Ubuntu clang-format version 11.0.0-2"
+# - Fedora: "clang-format version 11.0.0 (Fedora 11.0.0-2.fc33)"
+CLANG_FORMAT_VERSION="$(clang-format --version | sed "s/[^0-9\.]*\([0-9\.]*\).*/\1/")"
+CLANG_FORMAT_MAJOR="$(echo "$CLANG_FORMAT_VERSION" | cut -d. -f1)"
if [ "$CLANG_FORMAT_MAJOR" != "$RECOMMENDED_CLANG_FORMAT_MAJOR" ]; then
echo "Warning: Your clang-format binary is the wrong version ($CLANG_FORMAT_VERSION, expected $RECOMMENDED_CLANG_FORMAT_MAJOR.x.x)."
diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp
index c7fdf56af4..f22e74cafb 100644
--- a/modules/bmp/image_loader_bmp.cpp
+++ b/modules/bmp/image_loader_bmp.cpp
@@ -130,23 +130,19 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
line_ptr += 1;
} break;
case 24: {
- uint32_t color = *((uint32_t *)line_ptr);
-
- write_buffer[index + 2] = color & 0xff;
- write_buffer[index + 1] = (color >> 8) & 0xff;
- write_buffer[index + 0] = (color >> 16) & 0xff;
+ write_buffer[index + 2] = line_ptr[0];
+ write_buffer[index + 1] = line_ptr[1];
+ write_buffer[index + 0] = line_ptr[2];
write_buffer[index + 3] = 0xff;
index += 4;
line_ptr += 3;
} break;
case 32: {
- uint32_t color = *((uint32_t *)line_ptr);
-
- write_buffer[index + 2] = color & 0xff;
- write_buffer[index + 1] = (color >> 8) & 0xff;
- write_buffer[index + 0] = (color >> 16) & 0xff;
- write_buffer[index + 3] = color >> 24;
+ write_buffer[index + 2] = line_ptr[0];
+ write_buffer[index + 1] = line_ptr[1];
+ write_buffer[index + 0] = line_ptr[2];
+ write_buffer[index + 3] = line_ptr[3];
index += 4;
line_ptr += 4;
@@ -172,11 +168,9 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
const uint8_t *cb = p_color_buffer;
for (unsigned int i = 0; i < color_table_size; ++i) {
- uint32_t color = *((uint32_t *)cb);
-
- pal[i * 4 + 0] = (color >> 16) & 0xff;
- pal[i * 4 + 1] = (color >> 8) & 0xff;
- pal[i * 4 + 2] = (color)&0xff;
+ pal[i * 4 + 0] = cb[2];
+ pal[i * 4 + 1] = cb[1];
+ pal[i * 4 + 2] = cb[0];
pal[i * 4 + 3] = 0xff;
cb += 4;
diff --git a/modules/gdnative/include/gdnative/callable.h b/modules/gdnative/include/gdnative/callable.h
index b84b0c1f1f..1d52ca7a68 100644
--- a/modules/gdnative/include/gdnative/callable.h
+++ b/modules/gdnative/include/gdnative/callable.h
@@ -37,6 +37,7 @@ extern "C" {
#include <stdint.h>
+// Alignment hardcoded in `core/variant/callable.h`.
#define GODOT_CALLABLE_SIZE (16)
#ifndef GODOT_CORE_API_GODOT_CALLABLE_TYPE_DEFINED
diff --git a/modules/gdnative/include/gdnative/signal.h b/modules/gdnative/include/gdnative/signal.h
index f4dc17e089..41a76d0510 100644
--- a/modules/gdnative/include/gdnative/signal.h
+++ b/modules/gdnative/include/gdnative/signal.h
@@ -37,6 +37,7 @@ extern "C" {
#include <stdint.h>
+// Alignment hardcoded in `core/variant/callable.h`.
#define GODOT_SIGNAL_SIZE (16)
#ifndef GODOT_CORE_API_GODOT_SIGNAL_TYPE_DEFINED
diff --git a/modules/raycast/config.py b/modules/raycast/config.py
index 26493da41b..3da9ace9d8 100644
--- a/modules/raycast/config.py
+++ b/modules/raycast/config.py
@@ -1,10 +1,16 @@
def can_build(env, platform):
+ # Depends on Embree library, which supports only x86_64 (originally)
+ # and aarch64 (thanks to the embree-aarch64 fork).
+
if platform == "android":
- return env["android_arch"] in ["arm64v8", "x86", "x86_64"]
+ return env["android_arch"] in ["arm64v8", "x86_64"]
if platform == "javascript":
return False # No SIMD support yet
+ if env["bits"] == "32":
+ return False
+
return True
diff --git a/modules/raycast/lightmap_raycaster.cpp b/modules/raycast/lightmap_raycaster.cpp
index 56bdb5900b..0583acc119 100644
--- a/modules/raycast/lightmap_raycaster.cpp
+++ b/modules/raycast/lightmap_raycaster.cpp
@@ -32,7 +32,9 @@
#include "lightmap_raycaster.h"
+#ifdef __SSE2__
#include <pmmintrin.h>
+#endif
LightmapRaycaster *LightmapRaycasterEmbree::create_embree_raycaster() {
return memnew(LightmapRaycasterEmbree);
@@ -171,8 +173,10 @@ void embree_error_handler(void *p_user_data, RTCError p_code, const char *p_str)
}
LightmapRaycasterEmbree::LightmapRaycasterEmbree() {
+#ifdef __SSE2__
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
+#endif
embree_device = rtcNewDevice(nullptr);
rtcSetDeviceErrorFunction(embree_device, &embree_error_handler, nullptr);
@@ -180,8 +184,10 @@ LightmapRaycasterEmbree::LightmapRaycasterEmbree() {
}
LightmapRaycasterEmbree::~LightmapRaycasterEmbree() {
+#ifdef __SSE2__
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_OFF);
_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_OFF);
+#endif
if (embree_scene != nullptr) {
rtcReleaseScene(embree_scene);
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 51cea39a43..7a32184139 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -61,7 +61,6 @@ bool DisplayServerAndroid::has_feature(Feature p_feature) const {
//case FEATURE_MOUSE_WARP:
//case FEATURE_NATIVE_DIALOG:
//case FEATURE_NATIVE_ICON:
- //case FEATURE_NATIVE_VIDEO:
//case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_CLIPBOARD:
case FEATURE_KEEP_SCREEN_ON:
diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub
index ee7b2f4ab5..58b574a72f 100644
--- a/platform/iphone/SCsub
+++ b/platform/iphone/SCsub
@@ -19,7 +19,6 @@ iphone_lib = [
"godot_view_gesture_recognizer.mm",
"device_metrics.m",
"keyboard_input_view.mm",
- "native_video_view.m",
]
env_ios = env.Clone()
diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h
index 7bf1557873..34c56382a4 100644
--- a/platform/iphone/display_server_iphone.h
+++ b/platform/iphone/display_server_iphone.h
@@ -190,12 +190,6 @@ public:
virtual void screen_set_keep_on(bool p_enable) override;
virtual bool screen_is_kept_on() const override;
- virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen = SCREEN_OF_MAIN_WINDOW) override;
- virtual bool native_video_is_playing() const override;
- virtual void native_video_pause() override;
- virtual void native_video_unpause() override;
- virtual void native_video_stop() override;
-
void resize_window(CGSize size);
};
diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm
index a852bea207..9e74de0842 100644
--- a/platform/iphone/display_server_iphone.mm
+++ b/platform/iphone/display_server_iphone.mm
@@ -36,7 +36,6 @@
#import "godot_view.h"
#include "ios.h"
#import "keyboard_input_view.h"
-#import "native_video_view.h"
#include "os_iphone.h"
#import "view_controller.h"
@@ -305,7 +304,6 @@ bool DisplayServerIPhone::has_feature(Feature p_feature) const {
// case FEATURE_MOUSE_WARP:
// case FEATURE_NATIVE_DIALOG:
// case FEATURE_NATIVE_ICON:
- // case FEATURE_NATIVE_VIDEO:
// case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_CLIPBOARD:
case FEATURE_KEEP_SCREEN_ON:
@@ -569,69 +567,6 @@ bool DisplayServerIPhone::screen_is_kept_on() const {
return [UIApplication sharedApplication].idleTimerDisabled;
}
-Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen) {
- FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
- bool exists = f && f->is_open();
-
- String user_data_dir = OSIPhone::get_singleton()->get_user_data_dir();
-
- if (!exists) {
- return FAILED;
- }
-
- String tempFile = OSIPhone::get_singleton()->get_user_data_dir();
-
- if (p_path.begins_with("res://")) {
- if (PackedData::get_singleton()->has_path(p_path)) {
- printf("Unable to play %s using the native player as it resides in a .pck file\n", p_path.utf8().get_data());
- return ERR_INVALID_PARAMETER;
- } else {
- p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path());
- }
- } else if (p_path.begins_with("user://")) {
- p_path = p_path.replace("user:/", user_data_dir);
- }
-
- memdelete(f);
-
- printf("Playing video: %s\n", p_path.utf8().get_data());
-
- String file_path = ProjectSettings::get_singleton()->globalize_path(p_path);
-
- NSString *filePath = [[NSString alloc] initWithUTF8String:file_path.utf8().get_data()];
- NSString *audioTrack = [NSString stringWithUTF8String:p_audio_track.utf8()];
- NSString *subtitleTrack = [NSString stringWithUTF8String:p_subtitle_track.utf8()];
-
- if (![AppDelegate.viewController playVideoAtPath:filePath
- volume:p_volume
- audio:audioTrack
- subtitle:subtitleTrack]) {
- return OK;
- }
-
- return FAILED;
-}
-
-bool DisplayServerIPhone::native_video_is_playing() const {
- return [AppDelegate.viewController.videoView isVideoPlaying];
-}
-
-void DisplayServerIPhone::native_video_pause() {
- if (native_video_is_playing()) {
- [AppDelegate.viewController.videoView pauseVideo];
- }
-}
-
-void DisplayServerIPhone::native_video_unpause() {
- [AppDelegate.viewController.videoView unpauseVideo];
-};
-
-void DisplayServerIPhone::native_video_stop() {
- if (native_video_is_playing()) {
- [AppDelegate.viewController.videoView stopVideo];
- }
-}
-
void DisplayServerIPhone::resize_window(CGSize viewSize) {
Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale();
diff --git a/platform/iphone/native_video_view.m b/platform/iphone/native_video_view.m
deleted file mode 100644
index f126749600..0000000000
--- a/platform/iphone/native_video_view.m
+++ /dev/null
@@ -1,266 +0,0 @@
-/*************************************************************************/
-/* native_video_view.m */
-/*************************************************************************/
-/* 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. */
-/*************************************************************************/
-
-#import "native_video_view.h"
-#import <AVFoundation/AVFoundation.h>
-
-@interface GodotNativeVideoView ()
-
-@property(strong, nonatomic) AVAsset *avAsset;
-@property(strong, nonatomic) AVPlayerItem *avPlayerItem;
-@property(strong, nonatomic) AVPlayer *avPlayer;
-@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer;
-@property(assign, nonatomic) CMTime videoCurrentTime;
-@property(assign, nonatomic) BOOL isVideoCurrentlyPlaying;
-
-@end
-
-@implementation GodotNativeVideoView
-
-- (instancetype)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
-
- if (self) {
- [self godot_commonInit];
- }
-
- return self;
-}
-
-- (instancetype)initWithCoder:(NSCoder *)coder {
- self = [super initWithCoder:coder];
-
- if (self) {
- [self godot_commonInit];
- }
-
- return self;
-}
-
-- (void)godot_commonInit {
- self.isVideoCurrentlyPlaying = NO;
- self.videoCurrentTime = kCMTimeZero;
-
- [self observeVideoAudio];
-}
-
-- (void)layoutSubviews {
- [super layoutSubviews];
-
- self.avPlayerLayer.frame = self.bounds;
-}
-
-- (void)observeVideoAudio {
- printf("******** adding observer for sound routing changes\n");
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(audioRouteChangeListenerCallback:)
- name:AVAudioSessionRouteChangeNotification
- object:nil];
-}
-
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
- if (object == self.avPlayerItem && [keyPath isEqualToString:@"status"]) {
- [self handleVideoOrPlayerStatus];
- }
-
- if (object == self.avPlayer && [keyPath isEqualToString:@"rate"]) {
- [self handleVideoPlayRate];
- }
-}
-
-// MARK: Video Audio
-
-- (void)audioRouteChangeListenerCallback:(NSNotification *)notification {
- printf("*********** route changed!\n");
- NSDictionary *interuptionDict = notification.userInfo;
-
- NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
-
- switch (routeChangeReason) {
- case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {
- NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable");
- NSLog(@"Headphone/Line plugged in");
- } break;
- case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: {
- NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
- NSLog(@"Headphone/Line was pulled. Resuming video play....");
- if ([self isVideoPlaying]) {
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
- [self.avPlayer play]; // NOTE: change this line according your current player implementation
- NSLog(@"resumed play");
- });
- }
- } break;
- case AVAudioSessionRouteChangeReasonCategoryChange: {
- // called at start - also when other audio wants to play
- NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
- } break;
- }
-}
-
-// MARK: Native Video Player
-
-- (void)handleVideoOrPlayerStatus {
- if (self.avPlayerItem.status == AVPlayerItemStatusFailed || self.avPlayer.status == AVPlayerStatusFailed) {
- [self stopVideo];
- }
-
- if (self.avPlayer.status == AVPlayerStatusReadyToPlay && self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && CMTimeCompare(self.videoCurrentTime, kCMTimeZero) == 0) {
- // NSLog(@"time: %@", self.video_current_time);
- [self.avPlayer seekToTime:self.videoCurrentTime];
- self.videoCurrentTime = kCMTimeZero;
- }
-}
-
-- (void)handleVideoPlayRate {
- NSLog(@"Player playback rate changed: %.5f", self.avPlayer.rate);
- if ([self isVideoPlaying] && self.avPlayer.rate == 0.0 && !self.avPlayer.error) {
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
- [self.avPlayer play]; // NOTE: change this line according your current player implementation
- NSLog(@"resumed play");
- });
-
- NSLog(@" . . . PAUSED (or just started)");
- }
-}
-
-- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack {
- self.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:filePath]];
-
- self.avPlayerItem = [AVPlayerItem playerItemWithAsset:self.avAsset];
- [self.avPlayerItem addObserver:self forKeyPath:@"status" options:0 context:nil];
-
- self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem];
- self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
-
- [self.avPlayer addObserver:self forKeyPath:@"status" options:0 context:nil];
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(playerItemDidReachEnd:)
- name:AVPlayerItemDidPlayToEndTimeNotification
- object:[self.avPlayer currentItem]];
-
- [self.avPlayer addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0];
-
- [self.avPlayerLayer setFrame:self.bounds];
- [self.layer addSublayer:self.avPlayerLayer];
- [self.avPlayer play];
-
- AVMediaSelectionGroup *audioGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
-
- NSMutableArray *allAudioParams = [NSMutableArray array];
- for (id track in audioGroup.options) {
- NSString *language = [[track locale] localeIdentifier];
- NSLog(@"subtitle lang: %@", language);
-
- if ([language isEqualToString:audioTrack]) {
- AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
- [audioInputParams setVolume:videoVolume atTime:kCMTimeZero];
- [audioInputParams setTrackID:[track trackID]];
- [allAudioParams addObject:audioInputParams];
-
- AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
- [audioMix setInputParameters:allAudioParams];
-
- [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup];
- [self.avPlayer.currentItem setAudioMix:audioMix];
-
- break;
- }
- }
-
- AVMediaSelectionGroup *subtitlesGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
- NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]];
-
- for (id track in useableTracks) {
- NSString *language = [[track locale] localeIdentifier];
- NSLog(@"subtitle lang: %@", language);
-
- if ([language isEqualToString:subtitleTrack]) {
- [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup];
- break;
- }
- }
-
- self.isVideoCurrentlyPlaying = YES;
-
- return true;
-}
-
-- (BOOL)isVideoPlaying {
- if (self.avPlayer.error) {
- printf("Error during playback\n");
- }
- return (self.avPlayer.rate > 0 && !self.avPlayer.error);
-}
-
-- (void)pauseVideo {
- self.videoCurrentTime = self.avPlayer.currentTime;
- [self.avPlayer pause];
- self.isVideoCurrentlyPlaying = NO;
-}
-
-- (void)unpauseVideo {
- [self.avPlayer play];
- self.isVideoCurrentlyPlaying = YES;
-}
-
-- (void)playerItemDidReachEnd:(NSNotification *)notification {
- [self stopVideo];
-}
-
-- (void)finishPlayingVideo {
- [self.avPlayer pause];
- [self.avPlayerLayer removeFromSuperlayer];
- self.avPlayerLayer = nil;
-
- if (self.avPlayerItem) {
- [self.avPlayerItem removeObserver:self forKeyPath:@"status"];
- self.avPlayerItem = nil;
- }
-
- if (self.avPlayer) {
- [self.avPlayer removeObserver:self forKeyPath:@"status"];
- self.avPlayer = nil;
- }
-
- self.avAsset = nil;
-
- self.isVideoCurrentlyPlaying = NO;
-}
-
-- (void)stopVideo {
- [self finishPlayingVideo];
-
- [self removeFromSuperview];
-}
-
-@end
diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm
index 51c4da2960..458834ce3a 100644
--- a/platform/iphone/os_iphone.mm
+++ b/platform/iphone/os_iphone.mm
@@ -301,10 +301,6 @@ void OSIPhone::on_focus_out() {
[AppDelegate.viewController.godotView stopRendering];
- if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) {
- DisplayServerIPhone::get_singleton()->native_video_pause();
- }
-
audio_driver.stop();
}
}
@@ -319,10 +315,6 @@ void OSIPhone::on_focus_in() {
[AppDelegate.viewController.godotView startRendering];
- if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) {
- DisplayServerIPhone::get_singleton()->native_video_unpause();
- }
-
audio_driver.start();
}
}
diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h
index 52fb6fbbf2..400145b8b7 100644
--- a/platform/iphone/view_controller.h
+++ b/platform/iphone/view_controller.h
@@ -37,11 +37,6 @@
@interface ViewController : UIViewController
@property(nonatomic, readonly, strong) GodotView *godotView;
-@property(nonatomic, readonly, strong) GodotNativeVideoView *videoView;
@property(nonatomic, readonly, strong) GodotKeyboardInputView *keyboardView;
-// MARK: Native Video Player
-
-- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack;
-
@end
diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm
index 6cef244567..2723ac4706 100644
--- a/platform/iphone/view_controller.mm
+++ b/platform/iphone/view_controller.mm
@@ -34,7 +34,6 @@
#import "godot_view.h"
#import "godot_view_renderer.h"
#import "keyboard_input_view.h"
-#import "native_video_view.h"
#include "os_iphone.h"
#import <AVFoundation/AVFoundation.h>
@@ -43,7 +42,6 @@
@interface ViewController () <GodotViewDelegate>
@property(strong, nonatomic) GodotViewRenderer *renderer;
-@property(strong, nonatomic) GodotNativeVideoView *videoView;
@property(strong, nonatomic) GodotKeyboardInputView *keyboardView;
@property(strong, nonatomic) UIView *godotLoadingOverlay;
@@ -151,10 +149,6 @@
}
- (void)dealloc {
- [self.videoView stopVideo];
-
- self.videoView = nil;
-
self.keyboardView = nil;
self.renderer = nil;
@@ -243,22 +237,4 @@
}
}
-// MARK: Native Video Player
-
-- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack {
- // If we are showing some video already, reuse existing view for new video.
- if (self.videoView) {
- return [self.videoView playVideoAtPath:filePath volume:videoVolume audio:audioTrack subtitle:subtitleTrack];
- } else {
- // Create autoresizing view for video playback.
- GodotNativeVideoView *videoView = [[GodotNativeVideoView alloc] initWithFrame:self.view.bounds];
- videoView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- [self.view addSubview:videoView];
-
- self.videoView = videoView;
-
- return [self.videoView playVideoAtPath:filePath volume:videoVolume audio:audioTrack subtitle:subtitleTrack];
- }
-}
-
@end
diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp
index 234e42376d..736a8a9a34 100644
--- a/platform/javascript/display_server_javascript.cpp
+++ b/platform/javascript/display_server_javascript.cpp
@@ -827,7 +827,6 @@ bool DisplayServerJavaScript::has_feature(Feature p_feature) const {
//case FEATURE_MOUSE_WARP:
//case FEATURE_NATIVE_DIALOG:
//case FEATURE_NATIVE_ICON:
- //case FEATURE_NATIVE_VIDEO:
//case FEATURE_WINDOW_TRANSPARENCY:
//case FEATURE_KEEP_SCREEN_ON:
//case FEATURE_ORIENTATION:
diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp
index 0fe95b0a8f..40771d1882 100644
--- a/platform/javascript/javascript_main.cpp
+++ b/platform/javascript/javascript_main.cpp
@@ -66,6 +66,11 @@ void main_loop_callback() {
int target_fps = Engine::get_singleton()->get_target_fps();
if (target_fps > 0) {
+ if (current_ticks - target_ticks > 1000000) {
+ // When the window loses focus, we stop getting updates and accumulate delay.
+ // For this reason, if the difference is too big, we reset target ticks to the current ticks.
+ target_ticks = current_ticks;
+ }
target_ticks += (uint64_t)(1000000 / target_fps);
}
if (os->main_loop_iterate()) {
diff --git a/platform/javascript/package-lock.json b/platform/javascript/package-lock.json
index b8c434b3dd..8bf5c52ff6 100644
--- a/platform/javascript/package-lock.json
+++ b/platform/javascript/package-lock.json
@@ -44,9 +44,9 @@
}
},
"@babel/parser": {
- "version": "7.13.4",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.4.tgz",
- "integrity": "sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA==",
+ "version": "7.14.1",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.1.tgz",
+ "integrity": "sha512-muUGEKu8E/ftMTPlNp+mc6zL3E9zKWmF5sDHZ5MSsoTP9Wyz64AhEf9kD08xYJ7w6Hdcu8H550ircnPyWSIF0Q==",
"dev": true
},
"@eslint/eslintrc": {
@@ -812,9 +812,9 @@
"dev": true
},
"hosted-git-info": {
- "version": "2.8.8",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
- "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"dev": true
},
"ignore": {
@@ -962,9 +962,8 @@
}
},
"jsdoc": {
- "version": "3.6.6",
- "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.6.tgz",
- "integrity": "sha512-znR99e1BHeyEkSvgDDpX0sTiTu+8aQyDl9DawrkOGZTTW8hv0deIFXx87114zJ7gRaDZKVQD/4tr1ifmJp9xhQ==",
+ "version": "github:jsdoc/jsdoc#544a992824631fc652183d8b0b4b1281dc961560",
+ "from": "github:jsdoc/jsdoc#544a992824631fc652183d8b0b4b1281dc961560",
"dev": true,
"requires": {
"@babel/parser": "^7.9.4",
@@ -975,12 +974,12 @@
"klaw": "^3.0.0",
"markdown-it": "^10.0.0",
"markdown-it-anchor": "^5.2.7",
- "marked": "^0.8.2",
+ "marked": "^2.0.3",
"mkdirp": "^1.0.4",
"requizzle": "^0.2.3",
"strip-json-comments": "^3.1.0",
"taffydb": "2.6.2",
- "underscore": "~1.10.2"
+ "underscore": "~1.12.1"
},
"dependencies": {
"escape-string-regexp": {
@@ -1069,9 +1068,9 @@
}
},
"lodash": {
- "version": "4.17.20",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
- "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"markdown-it": {
@@ -1094,9 +1093,9 @@
"dev": true
},
"marked": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz",
- "integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.3.tgz",
+ "integrity": "sha512-5otztIIcJfPc2qGTN8cVtOJEjNJZ0jwa46INMagrYfk0EvqtRuEHLsEe0LrFS0/q+ZRKT0+kXK7P2T1AN5lWRA==",
"dev": true
},
"mdurl": {
@@ -1689,9 +1688,9 @@
"dev": true
},
"underscore": {
- "version": "1.10.2",
- "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz",
- "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==",
+ "version": "1.12.1",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz",
+ "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==",
"dev": true
},
"uri-js": {
diff --git a/platform/javascript/package.json b/platform/javascript/package.json
index d9d272923e..53748503f9 100644
--- a/platform/javascript/package.json
+++ b/platform/javascript/package.json
@@ -23,6 +23,6 @@
"eslint": "^7.9.0",
"eslint-config-airbnb-base": "^14.2.0",
"eslint-plugin-import": "^2.22.0",
- "jsdoc": "^3.6.6"
+ "jsdoc": "github:jsdoc/jsdoc#544a992824631fc652183d8b0b4b1281dc961560"
}
}
diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp
index 12f030bd58..be69b2e5da 100644
--- a/platform/linuxbsd/display_server_x11.cpp
+++ b/platform/linuxbsd/display_server_x11.cpp
@@ -2606,7 +2606,6 @@ void DisplayServerX11::_window_changed(XEvent *event) {
}
#endif
- print_line("DisplayServer::_window_changed: " + itos(window_id) + " rect: " + new_rect);
if (!wd.rect_changed_callback.is_null()) {
Rect2i r = new_rect;
@@ -3833,8 +3832,6 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u
wd.position.y = xwa.y;
wd.size.width = xwa.width;
wd.size.height = xwa.height;
-
- print_line("DisplayServer::_create_window " + itos(id) + " want rect: " + p_rect + " got rect " + Rect2i(xwa.x, xwa.y, xwa.width, xwa.height));
}
//set cursor
diff --git a/platform/uwp/SCsub b/platform/uwp/SCsub
index 4358b0eead..71c402358f 100644
--- a/platform/uwp/SCsub
+++ b/platform/uwp/SCsub
@@ -3,7 +3,6 @@
Import("env")
files = [
- "thread_uwp.cpp",
"#platform/windows/key_mapping_windows.cpp",
"#platform/windows/windows_terminal_logger.cpp",
"joypad_uwp.cpp",
diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp
index 8a0631a614..774a194e39 100644
--- a/scene/2d/gpu_particles_2d.cpp
+++ b/scene/2d/gpu_particles_2d.cpp
@@ -369,6 +369,7 @@ void GPUParticles2D::_bind_methods() {
GPUParticles2D::GPUParticles2D() {
particles = RS::get_singleton()->particles_create();
+ RS::get_singleton()->particles_set_mode(particles, RS::PARTICLES_MODE_2D);
one_shot = false; // Needed so that set_emitting doesn't access uninitialized values
set_emitting(true);
diff --git a/scene/2d/polygon_2d.cpp b/scene/2d/polygon_2d.cpp
index 1a7038bb80..21083e6a4b 100644
--- a/scene/2d/polygon_2d.cpp
+++ b/scene/2d/polygon_2d.cpp
@@ -302,17 +302,18 @@ void Polygon2D::_notification(int p_what) {
colors.write[i] = color_r[i];
}
} else {
- colors.push_back(color);
+ colors.resize(len);
+ for (int i = 0; i < len; i++) {
+ colors.write[i] = color;
+ }
}
+ Vector<int> index_array;
+
if (invert || polygons.size() == 0) {
- Vector<int> indices = Geometry2D::triangulate_polygon(points);
- if (indices.size()) {
- RS::get_singleton()->canvas_item_add_triangle_array(get_canvas_item(), indices, points, colors, uvs, bones, weights, texture.is_valid() ? texture->get_rid() : RID(), -1);
- }
+ index_array = Geometry2D::triangulate_polygon(points);
} else {
//draw individual polygons
- Vector<int> total_indices;
for (int i = 0; i < polygons.size(); i++) {
Vector<int> src_indices = polygons[i];
int ic = src_indices.size();
@@ -333,18 +334,38 @@ void Polygon2D::_notification(int p_what) {
int ic2 = indices.size();
const int *r2 = indices.ptr();
- int bic = total_indices.size();
- total_indices.resize(bic + ic2);
- int *w2 = total_indices.ptrw();
+ int bic = index_array.size();
+ index_array.resize(bic + ic2);
+ int *w2 = index_array.ptrw();
for (int j = 0; j < ic2; j++) {
w2[j + bic] = r[r2[j]];
}
}
+ }
+
+ RS::get_singleton()->mesh_clear(mesh);
+
+ if (index_array.size()) {
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ arr[RS::ARRAY_VERTEX] = points;
+ if (uvs.size() == points.size()) {
+ arr[RS::ARRAY_TEX_UV] = uvs;
+ }
+ if (colors.size() == points.size()) {
+ arr[RS::ARRAY_COLOR] = colors;
+ }
- if (total_indices.size()) {
- RS::get_singleton()->canvas_item_add_triangle_array(get_canvas_item(), total_indices, points, colors, uvs, bones, weights, texture.is_valid() ? texture->get_rid() : RID());
+ if (bones.size() == points.size() * 4) {
+ arr[RS::ARRAY_BONES] = bones;
+ arr[RS::ARRAY_WEIGHTS] = weights;
}
+
+ arr[RS::ARRAY_INDEX] = index_array;
+
+ RS::get_singleton()->mesh_add_surface_from_arrays(mesh, RS::PRIMITIVE_TRIANGLES, arr, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES);
+ RS::get_singleton()->canvas_item_add_mesh(get_canvas_item(), mesh, Transform2D(), Color(), texture.is_valid() ? texture->get_rid() : RID());
}
} break;
@@ -655,4 +676,9 @@ void Polygon2D::_bind_methods() {
}
Polygon2D::Polygon2D() {
+ mesh = RS::get_singleton()->mesh_create();
+}
+
+Polygon2D::~Polygon2D() {
+ RS::get_singleton()->free(mesh);
}
diff --git a/scene/2d/polygon_2d.h b/scene/2d/polygon_2d.h
index c207024a53..f9f36ff9a2 100644
--- a/scene/2d/polygon_2d.h
+++ b/scene/2d/polygon_2d.h
@@ -72,6 +72,8 @@ class Polygon2D : public Node2D {
void _skeleton_bone_setup_changed();
+ RID mesh;
+
protected:
void _notification(int p_what);
static void _bind_methods();
@@ -149,6 +151,7 @@ public:
NodePath get_skeleton() const;
Polygon2D();
+ ~Polygon2D();
};
#endif // POLYGON_2D_H
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index 532a795b7c..0afead0863 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -30,260 +30,323 @@
#include "tile_map.h"
-#include "collision_object_2d.h"
#include "core/io/marshalls.h"
+#include "core/math/geometry_2d.h"
#include "core/os/os.h"
-#include "scene/2d/area_2d.h"
-#include "servers/navigation_server_2d.h"
-#include "servers/physics_server_2d.h"
-int TileMap::_get_quadrant_size() const {
- if (use_y_sort) {
- return 1;
- } else {
- return quadrant_size;
- }
+void TileMapPattern::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) {
+ ERR_FAIL_COND_MSG(p_coords.x < 0 || p_coords.y < 0, vformat("Cannot set cell with negative coords in a TileMapPattern. Wrong coords: %s", p_coords));
+
+ size = size.max(p_coords + Vector2i(1, 1));
+ pattern[p_coords] = TileMapCell(p_source_id, p_atlas_coords, p_alternative_tile);
}
-void TileMap::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_ENTER_TREE: {
- if (use_parent) {
- _clear_quadrants();
- collision_parent = Object::cast_to<CollisionObject2D>(get_parent());
- }
+bool TileMapPattern::has_cell(const Vector2i &p_coords) const {
+ return pattern.has(p_coords);
+}
- pending_update = true;
- _recreate_quadrants();
- update_dirty_quadrants();
- RID space = get_world_2d()->get_space();
- _update_quadrant_transform();
- _update_quadrant_space(space);
- update_configuration_warnings();
+void TileMapPattern::remove_cell(const Vector2i &p_coords, bool p_update_size) {
+ ERR_FAIL_COND(!pattern.has(p_coords));
- } break;
+ pattern.erase(p_coords);
+ if (p_update_size) {
+ size = Vector2i();
+ for (Map<Vector2i, TileMapCell>::Element *E = pattern.front(); E; E = E->next()) {
+ size = size.max(E->key() + Vector2i(1, 1));
+ }
+ }
+}
- case NOTIFICATION_EXIT_TREE: {
- _update_quadrant_space(RID());
- for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
- Quadrant &q = E->get();
- for (Map<PosKey, Quadrant::NavPoly>::Element *F = q.navpoly_ids.front(); F; F = F->next()) {
- NavigationServer2D::get_singleton()->region_set_map(F->get().region, RID());
- }
- q.navpoly_ids.clear();
+int TileMapPattern::get_cell_source_id(const Vector2i &p_coords) const {
+ ERR_FAIL_COND_V(!pattern.has(p_coords), -1);
- if (collision_parent) {
- collision_parent->remove_shape_owner(q.shape_owner_id);
- q.shape_owner_id = -1;
- }
+ return pattern[p_coords].source_id;
+}
- for (Map<PosKey, Quadrant::Occluder>::Element *F = q.occluder_instances.front(); F; F = F->next()) {
- RS::get_singleton()->free(F->get().id);
- }
- q.occluder_instances.clear();
- }
+Vector2i TileMapPattern::get_cell_atlas_coords(const Vector2i &p_coords) const {
+ ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetAtlasSource::INVALID_ATLAS_COORDS);
- collision_parent = nullptr;
- } break;
+ return pattern[p_coords].get_atlas_coords();
+}
- case NOTIFICATION_TRANSFORM_CHANGED: {
- //move stuff
- _update_quadrant_transform();
+int TileMapPattern::get_cell_alternative_tile(const Vector2i &p_coords) const {
+ ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
- } break;
- case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: {
- if (use_parent) {
- _recreate_quadrants();
- }
+ return pattern[p_coords].alternative_tile;
+}
- } break;
+TypedArray<Vector2i> TileMapPattern::get_used_cells() const {
+ // Returns the cells used in the tilemap.
+ TypedArray<Vector2i> a;
+ a.resize(pattern.size());
+ int i = 0;
+ for (Map<Vector2i, TileMapCell>::Element *E = pattern.front(); E; E = E->next()) {
+ Vector2i p(E->key().x, E->key().y);
+ a[i++] = p;
}
+
+ return a;
}
-void TileMap::_update_quadrant_space(const RID &p_space) {
- if (!use_parent) {
- for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
- Quadrant &q = E->get();
- PhysicsServer2D::get_singleton()->body_set_space(q.body, p_space);
- }
- }
+Vector2i TileMapPattern::get_size() const {
+ return size;
}
-void TileMap::_update_quadrant_transform() {
- if (!is_inside_tree()) {
- return;
+void TileMapPattern::set_size(const Vector2i &p_size) {
+ for (Map<Vector2i, TileMapCell>::Element *E = pattern.front(); E; E = E->next()) {
+ Vector2i coords = E->key();
+ if (p_size.x <= coords.x || p_size.y <= coords.y) {
+ ERR_FAIL_MSG(vformat("Cannot set pattern size to %s, it contains a tile at %s. Size can only be increased.", p_size, coords));
+ };
}
- Transform2D global_transform = get_global_transform();
+ size = p_size;
+}
- Transform2D local_transform;
- if (collision_parent) {
- local_transform = get_transform();
- }
+bool TileMapPattern::is_empty() const {
+ return pattern.is_empty();
+};
- for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
- Quadrant &q = E->get();
- Transform2D xform;
- xform.set_origin(q.pos);
+void TileMapPattern::clear() {
+ size = Vector2i();
+ pattern.clear();
+};
- if (!use_parent) {
- xform = global_transform * xform;
- PhysicsServer2D::get_singleton()->body_set_state(q.body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
- }
+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("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);
+ ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "coords"), &TileMapPattern::get_cell_atlas_coords);
+ ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "coords"), &TileMapPattern::get_cell_alternative_tile);
- if (bake_navigation) {
- for (Map<PosKey, Quadrant::NavPoly>::Element *F = q.navpoly_ids.front(); F; F = F->next()) {
- NavigationServer2D::get_singleton()->region_set_transform(F->get().region, F->get().xform);
+ ClassDB::bind_method(D_METHOD("get_used_cells"), &TileMapPattern::get_used_cells);
+ ClassDB::bind_method(D_METHOD("get_size"), &TileMapPattern::get_size);
+ ClassDB::bind_method(D_METHOD("set_size", "size"), &TileMapPattern::set_size);
+ ClassDB::bind_method(D_METHOD("is_empty"), &TileMapPattern::is_empty);
+}
+
+Vector2i TileMap::transform_coords_layout(Vector2i p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout) {
+ // Transform to stacked layout.
+ Vector2i output = p_coords;
+ if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) {
+ SWAP(output.x, output.y);
+ }
+ switch (p_from_layout) {
+ case TileSet::TILE_LAYOUT_STACKED:
+ break;
+ case TileSet::TILE_LAYOUT_STACKED_OFFSET:
+ if (output.y % 2) {
+ output.x -= 1;
}
- }
+ break;
+ case TileSet::TILE_LAYOUT_STAIRS_RIGHT:
+ case TileSet::TILE_LAYOUT_STAIRS_DOWN:
+ if ((p_from_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) {
+ if (output.y < 0 && bool(output.y % 2)) {
+ output = Vector2i(output.x + output.y / 2 - 1, output.y);
+ } else {
+ output = Vector2i(output.x + output.y / 2, output.y);
+ }
+ } else {
+ if (output.x < 0 && bool(output.x % 2)) {
+ output = Vector2i(output.x / 2 - 1, output.x + output.y * 2);
+ } else {
+ output = Vector2i(output.x / 2, output.x + output.y * 2);
+ }
+ }
+ break;
+ case TileSet::TILE_LAYOUT_DIAMOND_RIGHT:
+ case TileSet::TILE_LAYOUT_DIAMOND_DOWN:
+ if ((p_from_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) {
+ if ((output.x + output.y) < 0 && (output.x - output.y) % 2) {
+ output = Vector2i((output.x + output.y) / 2 - 1, output.y - output.x);
+ } else {
+ output = Vector2i((output.x + output.y) / 2, -output.x + output.y);
+ }
+ } else {
+ if ((output.x - output.y) < 0 && (output.x + output.y) % 2) {
+ output = Vector2i((output.x - output.y) / 2 - 1, output.x + output.y);
+ } else {
+ output = Vector2i((output.x - output.y) / 2, output.x + output.y);
+ }
+ }
+ break;
+ }
- for (Map<PosKey, Quadrant::Occluder>::Element *F = q.occluder_instances.front(); F; F = F->next()) {
- RS::get_singleton()->canvas_light_occluder_set_transform(F->get().id, global_transform * F->get().xform);
- }
+ switch (p_to_layout) {
+ case TileSet::TILE_LAYOUT_STACKED:
+ break;
+ case TileSet::TILE_LAYOUT_STACKED_OFFSET:
+ if (output.y % 2) {
+ output.x += 1;
+ }
+ break;
+ case TileSet::TILE_LAYOUT_STAIRS_RIGHT:
+ case TileSet::TILE_LAYOUT_STAIRS_DOWN:
+ if ((p_to_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) {
+ if (output.y < 0 && (output.y % 2)) {
+ output = Vector2i(output.x - output.y / 2 + 1, output.y);
+ } else {
+ output = Vector2i(output.x - output.y / 2, output.y);
+ }
+ } else {
+ if (output.y % 2) {
+ if (output.y < 0) {
+ output = Vector2i(2 * output.x + 1, -output.x + output.y / 2 - 1);
+ } else {
+ output = Vector2i(2 * output.x + 1, -output.x + output.y / 2);
+ }
+ } else {
+ output = Vector2i(2 * output.x, -output.x + output.y / 2);
+ }
+ }
+ break;
+ case TileSet::TILE_LAYOUT_DIAMOND_RIGHT:
+ case TileSet::TILE_LAYOUT_DIAMOND_DOWN:
+ if ((p_to_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) {
+ if (output.y % 2) {
+ if (output.y > 0) {
+ output = Vector2i(output.x - output.y / 2, output.x + output.y / 2 + 1);
+ } else {
+ output = Vector2i(output.x - output.y / 2 + 1, output.x + output.y / 2);
+ }
+ } else {
+ output = Vector2i(output.x - output.y / 2, output.x + output.y / 2);
+ }
+ } else {
+ if (output.y % 2) {
+ if (output.y < 0) {
+ output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2 - 1);
+ } else {
+ output = Vector2i(output.x + output.y / 2 + 1, -output.x + output.y / 2);
+ }
+ } else {
+ output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2);
+ }
+ }
+ break;
}
-}
-void TileMap::set_tileset(const Ref<TileSet> &p_tileset) {
- if (tile_set.is_valid()) {
- tile_set->disconnect("changed", callable_mp(this, &TileMap::_recreate_quadrants));
+ if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) {
+ SWAP(output.x, output.y);
}
- _clear_quadrants();
- tile_set = p_tileset;
+ return output;
+}
- if (tile_set.is_valid()) {
- tile_set->connect("changed", callable_mp(this, &TileMap::_recreate_quadrants));
+int TileMap::get_effective_quadrant_size() const {
+ // When using YSort, the quadrant size is reduced to 1 to have one CanvasItem per quadrant
+ if (tile_set.is_valid() && tile_set->is_y_sorting()) {
+ return 1;
} else {
- clear();
+ return quadrant_size;
}
+}
- _recreate_quadrants();
- emit_signal("settings_changed");
+Vector2i TileMap::_coords_to_quadrant_coords(const Vector2i &p_coords) const {
+ int quadrant_size = get_effective_quadrant_size();
+
+ // Rounding down, instead of simply rounding towards zero (truncating)
+ return Vector2i(
+ p_coords.x > 0 ? p_coords.x / quadrant_size : (p_coords.x - (quadrant_size - 1)) / quadrant_size,
+ p_coords.y > 0 ? p_coords.y / quadrant_size : (p_coords.y - (quadrant_size - 1)) / quadrant_size);
+}
+
+void TileMap::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ pending_update = true;
+ _recreate_quadrants();
+ } break;
+ case NOTIFICATION_EXIT_TREE: {
+ _clear_quadrants();
+ } break;
+ }
+
+ // Transfers the notification to tileset plugins.
+ if (tile_set.is_valid()) {
+ for (int i = 0; i < tile_set->get_tile_set_atlas_plugins().size(); i++) {
+ tile_set->get_tile_set_atlas_plugins()[i]->tilemap_notification(this, p_what);
+ }
+ }
}
Ref<TileSet> TileMap::get_tileset() const {
return tile_set;
}
-void TileMap::set_cell_size(Size2 p_size) {
- ERR_FAIL_COND(p_size.x < 1 || p_size.y < 1);
+void TileMap::set_tileset(const Ref<TileSet> &p_tileset) {
+ if (p_tileset == tile_set) {
+ return;
+ }
- _clear_quadrants();
- cell_size = p_size;
- _recreate_quadrants();
- emit_signal("settings_changed");
+ // Set the tileset, registering to its changes.
+ if (tile_set.is_valid()) {
+ tile_set->disconnect("changed", callable_mp(this, &TileMap::_make_all_quadrants_dirty));
+ tile_set->disconnect("changed", callable_mp(this, &TileMap::_tile_set_changed));
+ }
+
+ if (!p_tileset.is_valid()) {
+ _clear_quadrants();
+ }
+
+ tile_set = p_tileset;
+
+ if (tile_set.is_valid()) {
+ tile_set->connect("changed", callable_mp(this, &TileMap::_make_all_quadrants_dirty), varray(true));
+ tile_set->connect("changed", callable_mp(this, &TileMap::_tile_set_changed));
+ _recreate_quadrants();
+ }
+
+ emit_signal("changed");
}
-Size2 TileMap::get_cell_size() const {
- return cell_size;
+int TileMap::get_quadrant_size() const {
+ return quadrant_size;
}
void TileMap::set_quadrant_size(int p_size) {
- ERR_FAIL_COND_MSG(p_size < 1, "Quadrant size cannot be smaller than 1.");
+ ERR_FAIL_COND_MSG(p_size < 1, "TileMapQuadrant size cannot be smaller than 1.");
- _clear_quadrants();
quadrant_size = p_size;
_recreate_quadrants();
- emit_signal("settings_changed");
-}
-
-int TileMap::get_quadrant_size() const {
- return quadrant_size;
+ emit_signal("changed");
}
-void TileMap::_fix_cell_transform(Transform2D &xform, const Cell &p_cell, const Vector2 &p_offset, const Size2 &p_sc) {
+void TileMap::_fix_cell_transform(Transform2D &xform, const TileMapCell &p_cell, const Vector2 &p_offset, const Size2 &p_sc) {
Size2 s = p_sc;
Vector2 offset = p_offset;
- if (compatibility_mode && !centered_textures) {
- if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) {
- offset.y += cell_size.y;
- } else if (tile_origin == TILE_ORIGIN_CENTER) {
- offset += cell_size / 2;
- }
-
- if (s.y > s.x) {
- if ((p_cell.flip_h && (p_cell.flip_v || p_cell.transpose)) || (p_cell.flip_v && !p_cell.transpose)) {
- offset.y += s.y - s.x;
- }
- } else if (s.y < s.x) {
- if ((p_cell.flip_v && (p_cell.flip_h || p_cell.transpose)) || (p_cell.flip_h && !p_cell.transpose)) {
- offset.x += s.x - s.y;
- }
- }
+ // Flip/transpose: update the tile transform.
+ TileSetSource *source = *tile_set->get_source(p_cell.source_id);
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (!atlas_source) {
+ return;
}
-
- if (p_cell.transpose) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(p_cell.get_atlas_coords(), p_cell.alternative_tile));
+ if (tile_data->get_transpose()) {
SWAP(xform.elements[0].x, xform.elements[0].y);
SWAP(xform.elements[1].x, xform.elements[1].y);
SWAP(offset.x, offset.y);
SWAP(s.x, s.y);
}
- if (p_cell.flip_h) {
+ if (tile_data->get_flip_h()) {
xform.elements[0].x = -xform.elements[0].x;
xform.elements[1].x = -xform.elements[1].x;
- if (compatibility_mode && !centered_textures) {
- if (tile_origin == TILE_ORIGIN_TOP_LEFT || tile_origin == TILE_ORIGIN_BOTTOM_LEFT) {
- offset.x = s.x - offset.x;
- } else if (tile_origin == TILE_ORIGIN_CENTER) {
- offset.x = s.x - offset.x / 2;
- }
- } else {
- offset.x = s.x - offset.x;
- }
+ offset.x = s.x - offset.x;
}
- if (p_cell.flip_v) {
+ if (tile_data->get_flip_v()) {
xform.elements[0].y = -xform.elements[0].y;
xform.elements[1].y = -xform.elements[1].y;
- if (compatibility_mode && !centered_textures) {
- if (tile_origin == TILE_ORIGIN_TOP_LEFT) {
- offset.y = s.y - offset.y;
- } else if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) {
- offset.y += s.y;
- } else if (tile_origin == TILE_ORIGIN_CENTER) {
- offset.y += s.y;
- }
- } else {
- offset.y = s.y - offset.y;
- }
+ offset.y = s.y - offset.y;
}
- if (centered_textures) {
- offset += cell_size / 2 - s / 2;
- }
xform.elements[2] += offset;
}
-void TileMap::_add_shape(int &shape_idx, const Quadrant &p_q, const Ref<Shape2D> &p_shape, const TileSet::ShapeData &p_shape_data, const Transform2D &p_xform, const Vector2 &p_metadata) {
- PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
-
- if (!use_parent) {
- ps->body_add_shape(p_q.body, p_shape->get_rid(), p_xform);
- ps->body_set_shape_metadata(p_q.body, shape_idx, p_metadata);
- ps->body_set_shape_as_one_way_collision(p_q.body, shape_idx, p_shape_data.one_way_collision, p_shape_data.one_way_collision_margin);
-
- } else if (collision_parent) {
- Transform2D xform = p_xform;
- xform.set_origin(xform.get_origin() + p_q.pos);
-
- collision_parent->shape_owner_add_shape(p_q.shape_owner_id, p_shape);
-
- int real_index = collision_parent->shape_owner_get_shape_index(p_q.shape_owner_id, shape_idx);
- RID rid = collision_parent->get_rid();
-
- if (Object::cast_to<Area2D>(collision_parent) != nullptr) {
- ps->area_set_shape_transform(rid, real_index, get_transform() * xform);
- } else {
- ps->body_set_shape_transform(rid, real_index, get_transform() * xform);
- ps->body_set_shape_metadata(rid, real_index, p_metadata);
- ps->body_set_shape_as_one_way_collision(rid, real_index, p_shape_data.one_way_collision, p_shape_data.one_way_collision_margin);
- }
- }
- shape_idx++;
-}
-
void TileMap::update_dirty_quadrants() {
if (!pending_update) {
return;
@@ -293,387 +356,47 @@ void TileMap::update_dirty_quadrants() {
return;
}
- RenderingServer *vs = RenderingServer::get_singleton();
- PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
- Vector2 tofs = get_cell_draw_offset();
- Vector2 qofs;
-
- SceneTree *st = SceneTree::get_singleton();
- Color debug_collision_color;
- Color debug_navigation_color;
-
- bool debug_shapes = st && st->is_debugging_collisions_hint();
- if (debug_shapes) {
- debug_collision_color = st->get_debug_collisions_color();
+ // Update the coords cache.
+ for (SelfList<TileMapQuadrant> *q = dirty_quadrant_list.first(); q; q = q->next()) {
+ q->self()->map_to_world.clear();
+ q->self()->world_to_map.clear();
+ for (Set<Vector2i>::Element *E = q->self()->cells.front(); E; E = E->next()) {
+ Vector2i pk = E->get();
+ Vector2i pk_world_coords = map_to_world(pk);
+ q->self()->map_to_world[pk] = pk_world_coords;
+ q->self()->world_to_map[pk_world_coords] = pk;
+ }
}
- bool debug_navigation = st && st->is_debugging_navigation_hint();
- if (debug_navigation) {
- debug_navigation_color = st->get_debug_navigation_color();
+ // Call the update_dirty_quadrant method on plugins.
+ for (int i = 0; i < tile_set->get_tile_set_atlas_plugins().size(); i++) {
+ tile_set->get_tile_set_atlas_plugins()[i]->update_dirty_quadrants(this, dirty_quadrant_list);
}
- while (dirty_quadrant_list.first()) {
- Quadrant &q = *dirty_quadrant_list.first()->self();
-
- for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) {
- vs->free(E->get());
- }
-
- q.canvas_items.clear();
-
- if (!use_parent) {
- ps->body_clear_shapes(q.body);
- } else if (collision_parent) {
- collision_parent->shape_owner_clear_shapes(q.shape_owner_id);
- }
- int shape_idx = 0;
-
- for (Map<PosKey, Quadrant::NavPoly>::Element *E = q.navpoly_ids.front(); E; E = E->next()) {
- NavigationServer2D::get_singleton()->region_set_map(E->get().region, RID());
- }
- q.navpoly_ids.clear();
-
- for (Map<PosKey, Quadrant::Occluder>::Element *E = q.occluder_instances.front(); E; E = E->next()) {
- RS::get_singleton()->free(E->get().id);
- }
- q.occluder_instances.clear();
- Ref<ShaderMaterial> prev_material;
- int prev_z_index = 0;
- RID prev_canvas_item;
- RID prev_debug_canvas_item;
-
- for (int i = 0; i < q.cells.size(); i++) {
- Map<PosKey, Cell>::Element *E = tile_map.find(q.cells[i]);
- Cell &c = E->get();
- //moment of truth
- if (!tile_set->has_tile(c.id)) {
- continue;
- }
- Ref<Texture2D> tex = tile_set->tile_get_texture(c.id);
- Vector2 tile_ofs = tile_set->tile_get_texture_offset(c.id);
-
- Vector2 wofs = _map_to_world(E->key().x, E->key().y);
- Vector2 offset = wofs - q.pos + tofs;
-
- if (!tex.is_valid()) {
- continue;
- }
-
- Ref<ShaderMaterial> mat = tile_set->tile_get_material(c.id);
- int z_index = tile_set->tile_get_z_index(c.id);
-
- if (tile_set->tile_get_tile_mode(c.id) == TileSet::AUTO_TILE ||
- tile_set->tile_get_tile_mode(c.id) == TileSet::ATLAS_TILE) {
- z_index += tile_set->autotile_get_z_index(c.id, Vector2(c.autotile_coord_x, c.autotile_coord_y));
- }
-
- RID canvas_item;
- RID debug_canvas_item;
-
- if (prev_canvas_item == RID() || prev_material != mat || prev_z_index != z_index) {
- canvas_item = vs->canvas_item_create();
- if (mat.is_valid()) {
- vs->canvas_item_set_material(canvas_item, mat->get_rid());
- }
- vs->canvas_item_set_parent(canvas_item, get_canvas_item());
- _update_item_material_state(canvas_item);
- Transform2D xform;
- xform.set_origin(q.pos);
- vs->canvas_item_set_transform(canvas_item, xform);
- vs->canvas_item_set_light_mask(canvas_item, get_light_mask());
- vs->canvas_item_set_z_index(canvas_item, z_index);
-
- vs->canvas_item_set_default_texture_filter(canvas_item, RS::CanvasItemTextureFilter(CanvasItem::get_texture_filter()));
- vs->canvas_item_set_default_texture_repeat(canvas_item, RS::CanvasItemTextureRepeat(CanvasItem::get_texture_repeat()));
-
- q.canvas_items.push_back(canvas_item);
-
- if (debug_shapes) {
- debug_canvas_item = vs->canvas_item_create();
- vs->canvas_item_set_parent(debug_canvas_item, canvas_item);
- vs->canvas_item_set_z_as_relative_to_parent(debug_canvas_item, false);
- vs->canvas_item_set_z_index(debug_canvas_item, RS::CANVAS_ITEM_Z_MAX - 1);
- q.canvas_items.push_back(debug_canvas_item);
- prev_debug_canvas_item = debug_canvas_item;
- }
-
- prev_canvas_item = canvas_item;
- prev_material = mat;
- prev_z_index = z_index;
-
- } else {
- canvas_item = prev_canvas_item;
- if (debug_shapes) {
- debug_canvas_item = prev_debug_canvas_item;
- }
- }
-
- Rect2 r = tile_set->tile_get_region(c.id);
- if (tile_set->tile_get_tile_mode(c.id) == TileSet::AUTO_TILE || tile_set->tile_get_tile_mode(c.id) == TileSet::ATLAS_TILE) {
- int spacing = tile_set->autotile_get_spacing(c.id);
- r.size = tile_set->autotile_get_size(c.id);
- r.position += (r.size + Vector2(spacing, spacing)) * Vector2(c.autotile_coord_x, c.autotile_coord_y);
- }
-
- Size2 s;
- if (r == Rect2()) {
- s = tex->get_size();
- } else {
- s = r.size;
- }
-
- Rect2 rect;
- rect.position = offset.floor();
- rect.size = s;
- rect.size.x += fp_adjust;
- rect.size.y += fp_adjust;
-
- if (compatibility_mode && !centered_textures) {
- if (rect.size.y > rect.size.x) {
- if ((c.flip_h && (c.flip_v || c.transpose)) || (c.flip_v && !c.transpose)) {
- tile_ofs.y += rect.size.y - rect.size.x;
- }
- } else if (rect.size.y < rect.size.x) {
- if ((c.flip_v && (c.flip_h || c.transpose)) || (c.flip_h && !c.transpose)) {
- tile_ofs.x += rect.size.x - rect.size.y;
- }
- }
- }
-
- if (c.transpose) {
- SWAP(tile_ofs.x, tile_ofs.y);
- if (centered_textures) {
- rect.position.x += cell_size.x / 2 - rect.size.y / 2;
- rect.position.y += cell_size.y / 2 - rect.size.x / 2;
- }
- } else if (centered_textures) {
- rect.position += cell_size / 2 - rect.size / 2;
- }
-
- if (c.flip_h) {
- rect.size.x = -rect.size.x;
- tile_ofs.x = -tile_ofs.x;
- }
-
- if (c.flip_v) {
- rect.size.y = -rect.size.y;
- tile_ofs.y = -tile_ofs.y;
- }
-
- if (compatibility_mode && !centered_textures) {
- if (tile_origin == TILE_ORIGIN_TOP_LEFT) {
- rect.position += tile_ofs;
-
- } else if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) {
- rect.position += tile_ofs;
-
- if (c.transpose) {
- if (c.flip_h) {
- rect.position.x -= cell_size.x;
- } else {
- rect.position.x += cell_size.x;
- }
- } else {
- if (c.flip_v) {
- rect.position.y -= cell_size.y;
- } else {
- rect.position.y += cell_size.y;
- }
- }
-
- } else if (tile_origin == TILE_ORIGIN_CENTER) {
- rect.position += tile_ofs;
-
- if (c.flip_h) {
- rect.position.x -= cell_size.x / 2;
- } else {
- rect.position.x += cell_size.x / 2;
- }
-
- if (c.flip_v) {
- rect.position.y -= cell_size.y / 2;
- } else {
- rect.position.y += cell_size.y / 2;
- }
- }
- } else {
- rect.position += tile_ofs;
- }
-
- Color modulate = tile_set->tile_get_modulate(c.id);
- Color self_modulate = get_self_modulate();
- modulate = Color(modulate.r * self_modulate.r, modulate.g * self_modulate.g,
- modulate.b * self_modulate.b, modulate.a * self_modulate.a);
- if (r == Rect2()) {
- tex->draw_rect(canvas_item, rect, false, modulate, c.transpose);
- } else {
- tex->draw_rect_region(canvas_item, rect, r, modulate, c.transpose, clip_uv);
- }
-
- Vector<TileSet::ShapeData> shapes = tile_set->tile_get_shapes(c.id);
-
- for (int j = 0; j < shapes.size(); j++) {
- Ref<Shape2D> shape = shapes[j].shape;
- if (shape.is_valid()) {
- if (tile_set->tile_get_tile_mode(c.id) == TileSet::SINGLE_TILE || (shapes[j].autotile_coord.x == c.autotile_coord_x && shapes[j].autotile_coord.y == c.autotile_coord_y)) {
- Transform2D xform;
- xform.set_origin(offset.floor());
-
- Vector2 shape_ofs = shapes[j].shape_transform.get_origin();
-
- _fix_cell_transform(xform, c, shape_ofs, s);
-
- xform *= shapes[j].shape_transform.untranslated();
-
- if (debug_canvas_item.is_valid()) {
- vs->canvas_item_add_set_transform(debug_canvas_item, xform);
- shape->draw(debug_canvas_item, debug_collision_color);
- }
-
- if (shape->has_meta("decomposed")) {
- Array _shapes = shape->get_meta("decomposed");
- for (int k = 0; k < _shapes.size(); k++) {
- Ref<ConvexPolygonShape2D> convex = _shapes[k];
- if (convex.is_valid()) {
- _add_shape(shape_idx, q, convex, shapes[j], xform, Vector2(E->key().x, E->key().y));
-#ifdef DEBUG_ENABLED
- } else {
- print_error("The TileSet assigned to the TileMap " + get_name() + " has an invalid convex shape.");
-#endif
- }
- }
- } else {
- _add_shape(shape_idx, q, shape, shapes[j], xform, Vector2(E->key().x, E->key().y));
- }
- }
- }
- }
-
- if (debug_canvas_item.is_valid()) {
- vs->canvas_item_add_set_transform(debug_canvas_item, Transform2D());
- }
-
- if (bake_navigation) {
- Ref<NavigationPolygon> navpoly;
- Vector2 npoly_ofs;
- if (tile_set->tile_get_tile_mode(c.id) == TileSet::AUTO_TILE || tile_set->tile_get_tile_mode(c.id) == TileSet::ATLAS_TILE) {
- navpoly = tile_set->autotile_get_navigation_polygon(c.id, Vector2(c.autotile_coord_x, c.autotile_coord_y));
- npoly_ofs = Vector2();
- } else {
- navpoly = tile_set->tile_get_navigation_polygon(c.id);
- npoly_ofs = tile_set->tile_get_navigation_polygon_offset(c.id);
- }
-
- if (navpoly.is_valid()) {
- Transform2D xform;
- xform.set_origin(offset.floor() + q.pos);
- _fix_cell_transform(xform, c, npoly_ofs, s);
-
- RID region = NavigationServer2D::get_singleton()->region_create();
- NavigationServer2D::get_singleton()->region_set_map(region, get_world_2d()->get_navigation_map());
- NavigationServer2D::get_singleton()->region_set_transform(region, xform);
- NavigationServer2D::get_singleton()->region_set_navpoly(region, navpoly);
-
- Quadrant::NavPoly np;
- np.region = region;
- np.xform = xform;
- q.navpoly_ids[E->key()] = np;
-
- if (debug_navigation) {
- RID debug_navigation_item = vs->canvas_item_create();
- vs->canvas_item_set_parent(debug_navigation_item, canvas_item);
- vs->canvas_item_set_z_as_relative_to_parent(debug_navigation_item, false);
- vs->canvas_item_set_z_index(debug_navigation_item, RS::CANVAS_ITEM_Z_MAX - 2); // Display one below collision debug
-
- if (debug_navigation_item.is_valid()) {
- Vector<Vector2> navigation_polygon_vertices = navpoly->get_vertices();
- int vsize = navigation_polygon_vertices.size();
-
- if (vsize > 2) {
- Vector<Color> colors;
- Vector<Vector2> vertices;
- vertices.resize(vsize);
- colors.resize(vsize);
- {
- const Vector2 *vr = navigation_polygon_vertices.ptr();
- for (int j = 0; j < vsize; j++) {
- vertices.write[j] = vr[j];
- colors.write[j] = debug_navigation_color;
- }
- }
-
- Vector<int> indices;
-
- for (int j = 0; j < navpoly->get_polygon_count(); j++) {
- Vector<int> polygon = navpoly->get_polygon(j);
-
- for (int k = 2; k < polygon.size(); k++) {
- int kofs[3] = { 0, k - 1, k };
- for (int l = 0; l < 3; l++) {
- int idx = polygon[kofs[l]];
- ERR_FAIL_INDEX(idx, vsize);
- indices.push_back(idx);
- }
- }
- }
- Transform2D navxform;
- navxform.set_origin(offset.floor());
- _fix_cell_transform(navxform, c, npoly_ofs, s);
-
- vs->canvas_item_set_transform(debug_navigation_item, navxform);
- vs->canvas_item_add_triangle_array(debug_navigation_item, indices, vertices, colors);
- }
- }
- }
- }
- }
-
- Ref<OccluderPolygon2D> occluder;
- if (tile_set->tile_get_tile_mode(c.id) == TileSet::AUTO_TILE || tile_set->tile_get_tile_mode(c.id) == TileSet::ATLAS_TILE) {
- occluder = tile_set->autotile_get_light_occluder(c.id, Vector2(c.autotile_coord_x, c.autotile_coord_y));
- } else {
- occluder = tile_set->tile_get_light_occluder(c.id);
- }
- if (occluder.is_valid()) {
- Vector2 occluder_ofs = tile_set->tile_get_occluder_offset(c.id);
- Transform2D xform;
- xform.set_origin(offset.floor() + q.pos);
- _fix_cell_transform(xform, c, occluder_ofs, s);
-
- RID orid = RS::get_singleton()->canvas_light_occluder_create();
- RS::get_singleton()->canvas_light_occluder_set_transform(orid, get_global_transform() * xform);
- RS::get_singleton()->canvas_light_occluder_set_polygon(orid, occluder->get_rid());
- RS::get_singleton()->canvas_light_occluder_attach_to_canvas(orid, get_canvas());
- RS::get_singleton()->canvas_light_occluder_set_light_mask(orid, occluder_light_mask);
- Quadrant::Occluder oc;
- oc.xform = xform;
- oc.id = orid;
- q.occluder_instances[E->key()] = oc;
- }
+ // Redraw the debug canvas_items.
+ RenderingServer *rs = RenderingServer::get_singleton();
+ for (SelfList<TileMapQuadrant> *q = dirty_quadrant_list.first(); q; q = q->next()) {
+ rs->canvas_item_clear(q->self()->debug_canvas_item);
+ Transform2D xform;
+ xform.set_origin(map_to_world(q->self()->coords * get_effective_quadrant_size()));
+ rs->canvas_item_set_transform(q->self()->debug_canvas_item, xform);
+ for (int i = 0; i < tile_set->get_tile_set_atlas_plugins().size(); i++) {
+ tile_set->get_tile_set_atlas_plugins()[i]->draw_quadrant_debug(this, q->self());
}
+ }
+ // Clear the list
+ while (dirty_quadrant_list.first()) {
dirty_quadrant_list.remove(dirty_quadrant_list.first());
- quadrant_order_dirty = true;
}
pending_update = false;
- if (quadrant_order_dirty) {
- int index = -(int64_t)0x80000000; //always must be drawn below children
- for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
- Quadrant &q = E->get();
- for (List<RID>::Element *F = q.canvas_items.front(); F; F = F->next()) {
- RS::get_singleton()->canvas_item_set_draw_index(F->get(), index++);
- }
- }
-
- quadrant_order_dirty = false;
- }
-
_recompute_rect_cache();
}
void TileMap::_recompute_rect_cache() {
+ // Compute the displayed area of the tilemap.
#ifdef DEBUG_ENABLED
if (!rect_cache_dirty) {
@@ -681,12 +404,12 @@ void TileMap::_recompute_rect_cache() {
}
Rect2 r_total;
- for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
+ for (Map<Vector2i, TileMapQuadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
Rect2 r;
- r.position = _map_to_world(E->key().x * _get_quadrant_size(), E->key().y * _get_quadrant_size());
- r.expand_to(_map_to_world(E->key().x * _get_quadrant_size() + _get_quadrant_size(), E->key().y * _get_quadrant_size()));
- r.expand_to(_map_to_world(E->key().x * _get_quadrant_size() + _get_quadrant_size(), E->key().y * _get_quadrant_size() + _get_quadrant_size()));
- r.expand_to(_map_to_world(E->key().x * _get_quadrant_size(), E->key().y * _get_quadrant_size() + _get_quadrant_size()));
+ r.position = map_to_world(E->key() * get_effective_quadrant_size());
+ r.expand_to(map_to_world((E->key() + Vector2i(1, 0)) * get_effective_quadrant_size()));
+ r.expand_to(map_to_world((E->key() + Vector2i(1, 1)) * get_effective_quadrant_size()));
+ r.expand_to(map_to_world((E->key() + Vector2i(0, 1)) * get_effective_quadrant_size()));
if (E == quadrant_map.front()) {
r_total = r;
} else {
@@ -702,83 +425,58 @@ void TileMap::_recompute_rect_cache() {
#endif
}
-Map<TileMap::PosKey, TileMap::Quadrant>::Element *TileMap::_create_quadrant(const PosKey &p_qk) {
- Transform2D xform;
- //xform.set_origin(Point2(p_qk.x,p_qk.y)*cell_size*quadrant_size);
- Quadrant q;
- q.pos = _map_to_world(p_qk.x * _get_quadrant_size(), p_qk.y * _get_quadrant_size());
- q.pos += get_cell_draw_offset();
- if (tile_origin == TILE_ORIGIN_CENTER) {
- q.pos += cell_size / 2;
- } else if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) {
- q.pos.y += cell_size.y;
- }
-
- xform.set_origin(q.pos);
- //q.canvas_item = RenderingServer::get_singleton()->canvas_item_create();
- if (!use_parent) {
- q.body = PhysicsServer2D::get_singleton()->body_create();
- PhysicsServer2D::get_singleton()->body_set_mode(q.body, use_kinematic ? PhysicsServer2D::BODY_MODE_KINEMATIC : PhysicsServer2D::BODY_MODE_STATIC);
-
- PhysicsServer2D::get_singleton()->body_attach_object_instance_id(q.body, get_instance_id());
- PhysicsServer2D::get_singleton()->body_set_collision_layer(q.body, collision_layer);
- PhysicsServer2D::get_singleton()->body_set_collision_mask(q.body, collision_mask);
- PhysicsServer2D::get_singleton()->body_set_param(q.body, PhysicsServer2D::BODY_PARAM_FRICTION, friction);
- PhysicsServer2D::get_singleton()->body_set_param(q.body, PhysicsServer2D::BODY_PARAM_BOUNCE, bounce);
-
- if (is_inside_tree()) {
- xform = get_global_transform() * xform;
- RID space = get_world_2d()->get_space();
- PhysicsServer2D::get_singleton()->body_set_space(q.body, space);
- }
+Map<Vector2i, TileMapQuadrant>::Element *TileMap::_create_quadrant(const Vector2i &p_qk) {
+ TileMapQuadrant q;
+ q.coords = p_qk;
- PhysicsServer2D::get_singleton()->body_set_state(q.body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
- } else if (collision_parent) {
- xform = get_transform() * xform;
- q.shape_owner_id = collision_parent->create_shape_owner(this);
- } else {
- q.shape_owner_id = -1;
+ rect_cache_dirty = true;
+
+ // Create the debug canvas item.
+ RenderingServer *rs = RenderingServer::get_singleton();
+ q.debug_canvas_item = rs->canvas_item_create();
+ rs->canvas_item_set_z_index(q.debug_canvas_item, RS::CANVAS_ITEM_Z_MAX - 1);
+ rs->canvas_item_set_parent(q.debug_canvas_item, get_canvas_item());
+
+ // Call the create_quadrant method on plugins
+ if (tile_set.is_valid()) {
+ for (int i = 0; i < tile_set->get_tile_set_atlas_plugins().size(); i++) {
+ tile_set->get_tile_set_atlas_plugins()[i]->create_quadrant(this, &q);
+ }
}
- rect_cache_dirty = true;
- quadrant_order_dirty = true;
return quadrant_map.insert(p_qk, q);
}
-void TileMap::_erase_quadrant(Map<PosKey, Quadrant>::Element *Q) {
- Quadrant &q = Q->get();
- if (!use_parent) {
- PhysicsServer2D::get_singleton()->free(q.body);
- } else if (collision_parent) {
- collision_parent->remove_shape_owner(q.shape_owner_id);
- }
+void TileMap::_erase_quadrant(Map<Vector2i, TileMapQuadrant>::Element *Q) {
+ // Remove a quadrant.
+ TileMapQuadrant *q = &(Q->get());
- for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) {
- RenderingServer::get_singleton()->free(E->get());
- }
- q.canvas_items.clear();
- if (q.dirty_list.in_list()) {
- dirty_quadrant_list.remove(&q.dirty_list);
+ // Call the cleanup_quadrant method on plugins.
+ if (tile_set.is_valid()) {
+ for (int i = 0; i < tile_set->get_tile_set_atlas_plugins().size(); i++) {
+ tile_set->get_tile_set_atlas_plugins()[i]->cleanup_quadrant(this, q);
+ }
}
- for (Map<PosKey, Quadrant::NavPoly>::Element *E = q.navpoly_ids.front(); E; E = E->next()) {
- NavigationServer2D::get_singleton()->region_set_map(E->get().region, RID());
+ // Remove the quadrant from the dirty_list if it is there.
+ if (q->dirty_list_element.in_list()) {
+ dirty_quadrant_list.remove(&(q->dirty_list_element));
}
- q.navpoly_ids.clear();
- for (Map<PosKey, Quadrant::Occluder>::Element *E = q.occluder_instances.front(); E; E = E->next()) {
- RS::get_singleton()->free(E->get().id);
- }
- q.occluder_instances.clear();
+ // Free the debug canvas item.
+ RenderingServer *rs = RenderingServer::get_singleton();
+ rs->free(q->debug_canvas_item);
quadrant_map.erase(Q);
rect_cache_dirty = true;
}
-void TileMap::_make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q, bool update) {
- Quadrant &q = Q->get();
- if (!q.dirty_list.in_list()) {
- dirty_quadrant_list.add(&q.dirty_list);
+void TileMap::_make_all_quadrants_dirty(bool p_update) {
+ // Make all quandrants dirty, then trigger an update later.
+ for (Map<Vector2i, TileMapQuadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
+ if (!E->value().dirty_list_element.in_list()) {
+ dirty_quadrant_list.add(&E->value().dirty_list_element);
+ }
}
if (pending_update) {
@@ -788,39 +486,68 @@ void TileMap::_make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q, bool updat
if (!is_inside_tree()) {
return;
}
-
- if (update) {
+ if (p_update) {
call_deferred("update_dirty_quadrants");
}
}
-void TileMap::set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x, bool p_flip_y, bool p_transpose) {
- set_cell(p_pos.x, p_pos.y, p_tile, p_flip_x, p_flip_y, p_transpose);
-}
+void TileMap::_make_quadrant_dirty(Map<Vector2i, TileMapQuadrant>::Element *Q, bool p_update) {
+ // Make the given quadrant dirty, then trigger an update later.
+ TileMapQuadrant &q = Q->get();
+ if (!q.dirty_list_element.in_list()) {
+ dirty_quadrant_list.add(&q.dirty_list_element);
+ }
-void TileMap::_set_celld(const Vector2 &p_pos, const Dictionary &p_data) {
- Variant v_pos_x = p_pos.x, v_pos_y = p_pos.y, v_tile = p_data["id"], v_flip_h = p_data["flip_h"], v_flip_v = p_data["flip_y"], v_transpose = p_data["transpose"], v_autotile_coord = p_data["auto_coord"];
- const Variant *args[7] = { &v_pos_x, &v_pos_y, &v_tile, &v_flip_h, &v_flip_v, &v_transpose, &v_autotile_coord };
- Callable::CallError ce;
- call("set_cell", args, 7, ce);
+ if (pending_update) {
+ return;
+ }
+ pending_update = true;
+ if (!is_inside_tree()) {
+ return;
+ }
+
+ if (p_update) {
+ call_deferred("update_dirty_quadrants");
+ }
}
-void TileMap::set_cell(int p_x, int p_y, int p_tile, bool p_flip_x, bool p_flip_y, bool p_transpose, Vector2 p_autotile_coord) {
- PosKey pk(p_x, p_y);
+void TileMap::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) {
+ // Set the current cell tile (using integer position).
+ Vector2i pk(p_coords);
+ Map<Vector2i, TileMapCell>::Element *E = tile_map.find(pk);
+
+ int source_id = p_source_id;
+ Vector2i atlas_coords = p_atlas_coords;
+ int alternative_tile = p_alternative_tile;
- Map<PosKey, Cell>::Element *E = tile_map.find(pk);
- if (!E && p_tile == INVALID_CELL) {
- return; //nothing to do
+ 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)) {
+ 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;
}
- PosKey qk = pk.to_quadrant(_get_quadrant_size());
- if (p_tile == INVALID_CELL) {
- //erase existing
+ if (!E && source_id == -1) {
+ return; // Nothing to do, the tile is already empty.
+ }
+
+ // Get the quadrant
+ Vector2i qk = _coords_to_quadrant_coords(pk);
+
+ Map<Vector2i, TileMapQuadrant>::Element *Q = quadrant_map.find(qk);
+
+ if (source_id == -1) {
+ // Erase existing cell in the tile map.
tile_map.erase(pk);
- Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk);
+
+ // Erase existing cell in the quadrant.
ERR_FAIL_COND(!Q);
- Quadrant &q = Q->get();
+ TileMapQuadrant &q = Q->get();
+
q.cells.erase(pk);
+
+ // Remove or make the quadrant dirty.
if (q.cells.size() == 0) {
_erase_quadrant(Q);
} else {
@@ -828,331 +555,232 @@ void TileMap::set_cell(int p_x, int p_y, int p_tile, bool p_flip_x, bool p_flip_
}
used_size_cache_dirty = true;
- return;
- }
-
- Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk);
-
- if (!E) {
- E = tile_map.insert(pk, Cell());
- if (!Q) {
- Q = _create_quadrant(qk);
- }
- Quadrant &q = Q->get();
- q.cells.insert(pk);
} else {
- ERR_FAIL_COND(!Q); // quadrant should exist...
-
- if (E->get().id == p_tile && E->get().flip_h == p_flip_x && E->get().flip_v == p_flip_y && E->get().transpose == p_transpose && E->get().autotile_coord_x == (uint16_t)p_autotile_coord.x && E->get().autotile_coord_y == (uint16_t)p_autotile_coord.y) {
- return; //nothing changed
- }
- }
-
- Cell &c = E->get();
-
- c.id = p_tile;
- c.flip_h = p_flip_x;
- c.flip_v = p_flip_y;
- c.transpose = p_transpose;
- c.autotile_coord_x = (uint16_t)p_autotile_coord.x;
- c.autotile_coord_y = (uint16_t)p_autotile_coord.y;
-
- _make_quadrant_dirty(Q);
- used_size_cache_dirty = true;
-}
-
-int TileMap::get_cellv(const Vector2 &p_pos) const {
- return get_cell(p_pos.x, p_pos.y);
-}
-
-void TileMap::make_bitmask_area_dirty(const Vector2 &p_pos) {
- for (int x = p_pos.x - 1; x <= p_pos.x + 1; x++) {
- for (int y = p_pos.y - 1; y <= p_pos.y + 1; y++) {
- PosKey p(x, y);
- if (dirty_bitmask.find(p) == nullptr) {
- dirty_bitmask.push_back(p);
- }
- }
- }
-}
-
-void TileMap::update_bitmask_area(const Vector2 &p_pos) {
- for (int x = p_pos.x - 1; x <= p_pos.x + 1; x++) {
- for (int y = p_pos.y - 1; y <= p_pos.y + 1; y++) {
- update_cell_bitmask(x, y);
- }
- }
-}
+ if (!E) {
+ // Insert a new cell in the tile map.
+ E = tile_map.insert(pk, TileMapCell());
-void TileMap::update_bitmask_region(const Vector2 &p_start, const Vector2 &p_end) {
- if ((p_end.x < p_start.x || p_end.y < p_start.y) || (p_end.x == p_start.x && p_end.y == p_start.y)) {
- Array a = get_used_cells();
- for (int i = 0; i < a.size(); i++) {
- Vector2 vector = (Vector2)a[i];
- update_cell_bitmask(vector.x, vector.y);
- }
- return;
- }
- for (int x = p_start.x - 1; x <= p_end.x + 1; x++) {
- for (int y = p_start.y - 1; y <= p_end.y + 1; y++) {
- update_cell_bitmask(x, y);
- }
- }
-}
-
-void TileMap::update_cell_bitmask(int p_x, int p_y) {
- ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot update cell bitmask if Tileset is not open.");
- PosKey p(p_x, p_y);
- Map<PosKey, Cell>::Element *E = tile_map.find(p);
- if (E != nullptr) {
- int id = get_cell(p_x, p_y);
- if (tile_set->tile_get_tile_mode(id) == TileSet::AUTO_TILE) {
- uint16_t mask = 0;
- if (tile_set->autotile_get_bitmask_mode(id) == TileSet::BITMASK_2X2) {
- if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) {
- mask |= TileSet::BIND_TOPLEFT;
- }
- if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) {
- mask |= TileSet::BIND_TOPRIGHT;
- }
- if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) {
- mask |= TileSet::BIND_BOTTOMLEFT;
- }
- if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) {
- mask |= TileSet::BIND_BOTTOMRIGHT;
- }
- } else {
- if (tile_set->autotile_get_bitmask_mode(id) == TileSet::BITMASK_3X3_MINIMAL) {
- if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) {
- mask |= TileSet::BIND_TOPLEFT;
- }
- if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) {
- mask |= TileSet::BIND_TOPRIGHT;
- }
- if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) {
- mask |= TileSet::BIND_BOTTOMLEFT;
- }
- if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) {
- mask |= TileSet::BIND_BOTTOMRIGHT;
- }
- } else {
- if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y - 1))) {
- mask |= TileSet::BIND_TOPLEFT;
- }
- if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y - 1))) {
- mask |= TileSet::BIND_TOPRIGHT;
- }
- if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y + 1))) {
- mask |= TileSet::BIND_BOTTOMLEFT;
- }
- if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y + 1))) {
- mask |= TileSet::BIND_BOTTOMRIGHT;
- }
- }
- if (tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1))) {
- mask |= TileSet::BIND_TOP;
- }
- if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) {
- mask |= TileSet::BIND_LEFT;
- }
- mask |= TileSet::BIND_CENTER;
- if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) {
- mask |= TileSet::BIND_RIGHT;
- }
- if (tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1))) {
- mask |= TileSet::BIND_BOTTOM;
- }
+ // Create a new quadrant if needed, then insert the cell if needed.
+ if (!Q) {
+ Q = _create_quadrant(qk);
}
- Vector2 coord = tile_set->autotile_get_subtile_for_bitmask(id, mask, this, Vector2(p_x, p_y));
- E->get().autotile_coord_x = (int)coord.x;
- E->get().autotile_coord_y = (int)coord.y;
+ TileMapQuadrant &q = Q->get();
+ q.cells.insert(pk);
- PosKey qk = p.to_quadrant(_get_quadrant_size());
- Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk);
- _make_quadrant_dirty(Q);
-
- } else if (tile_set->tile_get_tile_mode(id) == TileSet::SINGLE_TILE) {
- E->get().autotile_coord_x = 0;
- E->get().autotile_coord_y = 0;
- } else if (tile_set->tile_get_tile_mode(id) == TileSet::ATLAS_TILE) {
- if (tile_set->autotile_get_bitmask(id, Vector2(p_x, p_y)) == TileSet::BIND_CENTER) {
- Vector2 coord = tile_set->atlastile_get_subtile_by_priority(id, this, Vector2(p_x, p_y));
+ } else {
+ ERR_FAIL_COND(!Q); // TileMapQuadrant should exist...
- E->get().autotile_coord_x = (int)coord.x;
- E->get().autotile_coord_y = (int)coord.y;
+ if (E->get().source_id == source_id && E->get().get_atlas_coords() == atlas_coords && E->get().alternative_tile == alternative_tile) {
+ return; // Nothing changed.
}
}
- }
-}
-void TileMap::update_dirty_bitmask() {
- while (dirty_bitmask.size() > 0) {
- update_cell_bitmask(dirty_bitmask[0].x, dirty_bitmask[0].y);
- dirty_bitmask.pop_front();
- }
-}
+ TileMapCell &c = E->get();
-void TileMap::fix_invalid_tiles() {
- ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot fix invalid tiles if Tileset is not open.");
+ c.source_id = source_id;
+ c.set_atlas_coords(atlas_coords);
+ c.alternative_tile = alternative_tile;
- Map<PosKey, Cell> temp_tile_map = tile_map;
- for (Map<PosKey, Cell>::Element *E = temp_tile_map.front(); E; E = E->next()) {
- if (!tile_set->has_tile(get_cell(E->key().x, E->key().y))) {
- set_cell(E->key().x, E->key().y, INVALID_CELL);
- }
+ _make_quadrant_dirty(Q);
+ used_size_cache_dirty = true;
}
}
-int TileMap::get_cell(int p_x, int p_y) const {
- PosKey pk(p_x, p_y);
-
- const Map<PosKey, Cell>::Element *E = tile_map.find(pk);
+int TileMap::get_cell_source_id(const Vector2i &p_coords) const {
+ // Get a cell source id from position
+ const Map<Vector2i, TileMapCell>::Element *E = tile_map.find(p_coords);
if (!E) {
- return INVALID_CELL;
+ return -1;
}
- return E->get().id;
+ return E->get().source_id;
}
-bool TileMap::is_cell_x_flipped(int p_x, int p_y) const {
- PosKey pk(p_x, p_y);
-
- const Map<PosKey, Cell>::Element *E = tile_map.find(pk);
+Vector2i TileMap::get_cell_atlas_coords(const Vector2i &p_coords) const {
+ // Get a cell source id from position
+ const Map<Vector2i, TileMapCell>::Element *E = tile_map.find(p_coords);
if (!E) {
- return false;
+ return TileSetAtlasSource::INVALID_ATLAS_COORDS;
}
- return E->get().flip_h;
+ return E->get().get_atlas_coords();
}
-bool TileMap::is_cell_y_flipped(int p_x, int p_y) const {
- PosKey pk(p_x, p_y);
-
- const Map<PosKey, Cell>::Element *E = tile_map.find(pk);
+int TileMap::get_cell_alternative_tile(const Vector2i &p_coords) const {
+ // Get a cell source id from position
+ const Map<Vector2i, TileMapCell>::Element *E = tile_map.find(p_coords);
if (!E) {
- return false;
+ return TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
}
- return E->get().flip_v;
+ return E->get().alternative_tile;
}
-bool TileMap::is_cell_transposed(int p_x, int p_y) const {
- PosKey pk(p_x, p_y);
-
- const Map<PosKey, Cell>::Element *E = tile_map.find(pk);
+TileMapPattern *TileMap::get_pattern(TypedArray<Vector2i> p_coords_array) {
+ ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr);
- if (!E) {
- return false;
+ TileMapPattern *output = memnew(TileMapPattern);
+ if (p_coords_array.is_empty()) {
+ return output;
}
- return E->get().transpose;
-}
-
-void TileMap::set_cell_autotile_coord(int p_x, int p_y, const Vector2 &p_coord) {
- PosKey pk(p_x, p_y);
+ Vector2i min = Vector2i(p_coords_array[0]);
+ for (int i = 1; i < p_coords_array.size(); i++) {
+ min = min.min(p_coords_array[i]);
+ }
- const Map<PosKey, Cell>::Element *E = tile_map.find(pk);
+ Vector<Vector2i> coords_in_pattern_array;
+ coords_in_pattern_array.resize(p_coords_array.size());
+ Vector2i ensure_positive_offset;
+ for (int i = 0; i < p_coords_array.size(); i++) {
+ Vector2i coords = p_coords_array[i];
+ Vector2i coords_in_pattern = coords - min;
+ if (tile_set->get_tile_shape() != TileSet::TILE_SHAPE_SQUARE) {
+ if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED) {
+ if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(min.y % 2) && bool(coords_in_pattern.y % 2)) {
+ coords_in_pattern.x -= 1;
+ if (coords_in_pattern.x < 0) {
+ ensure_positive_offset.x = 1;
+ }
+ } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(min.x % 2) && bool(coords_in_pattern.x % 2)) {
+ coords_in_pattern.y -= 1;
+ if (coords_in_pattern.y < 0) {
+ ensure_positive_offset.y = 1;
+ }
+ }
+ } else if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED_OFFSET) {
+ if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(min.y % 2) && bool(coords_in_pattern.y % 2)) {
+ coords_in_pattern.x += 1;
+ } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(min.x % 2) && bool(coords_in_pattern.x % 2)) {
+ coords_in_pattern.y += 1;
+ }
+ }
+ }
+ coords_in_pattern_array.write[i] = coords_in_pattern;
+ }
- if (!E) {
- return;
+ for (int i = 0; i < coords_in_pattern_array.size(); i++) {
+ Vector2i coords = p_coords_array[i];
+ Vector2i coords_in_pattern = coords_in_pattern_array[i];
+ output->set_cell(coords_in_pattern + ensure_positive_offset, get_cell_source_id(coords), get_cell_atlas_coords(coords), get_cell_alternative_tile(coords));
}
- Cell c = E->get();
- c.autotile_coord_x = p_coord.x;
- c.autotile_coord_y = p_coord.y;
- tile_map[pk] = c;
+ return output;
+}
- PosKey qk = pk.to_quadrant(_get_quadrant_size());
- Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk);
+Vector2i TileMap::map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, const TileMapPattern *p_pattern) {
+ ERR_FAIL_COND_V(!p_pattern->has_cell(p_coords_in_pattern), Vector2i());
- if (!Q) {
- return;
+ Vector2i output = p_position_in_tilemap + p_coords_in_pattern;
+ if (tile_set->get_tile_shape() != TileSet::TILE_SHAPE_SQUARE) {
+ if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED) {
+ if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(p_position_in_tilemap.y % 2) && bool(p_coords_in_pattern.y % 2)) {
+ output.x += 1;
+ } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(p_position_in_tilemap.x % 2) && bool(p_coords_in_pattern.x % 2)) {
+ output.y += 1;
+ }
+ } else if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED_OFFSET) {
+ if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(p_position_in_tilemap.y % 2) && bool(p_coords_in_pattern.y % 2)) {
+ output.x -= 1;
+ } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(p_position_in_tilemap.x % 2) && bool(p_coords_in_pattern.x % 2)) {
+ output.y -= 1;
+ }
+ }
}
- _make_quadrant_dirty(Q);
+ return output;
}
-Vector2 TileMap::get_cell_autotile_coord(int p_x, int p_y) const {
- PosKey pk(p_x, p_y);
+void TileMap::set_pattern(Vector2i p_position, const TileMapPattern *p_pattern) {
+ ERR_FAIL_COND(!tile_set.is_valid());
- const Map<PosKey, Cell>::Element *E = tile_map.find(pk);
+ TypedArray<Vector2i> used_cells = p_pattern->get_used_cells();
+ for (int i = 0; i < used_cells.size(); i++) {
+ Vector2i coords = map_pattern(p_position, used_cells[i], p_pattern);
+ set_cell(coords, p_pattern->get_cell_source_id(coords), p_pattern->get_cell_atlas_coords(coords), p_pattern->get_cell_alternative_tile(coords));
+ }
+}
- if (!E) {
- return Vector2();
+TileMapCell TileMap::get_cell(const Vector2i &p_coords) const {
+ if (!tile_map.has(p_coords)) {
+ return TileMapCell();
+ } else {
+ return tile_map.find(p_coords)->get();
}
+}
- return Vector2(E->get().autotile_coord_x, E->get().autotile_coord_y);
+Map<Vector2i, TileMapQuadrant> &TileMap::get_quadrant_map() {
+ return quadrant_map;
+}
+
+void TileMap::fix_invalid_tiles() {
+ ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot fix invalid tiles if Tileset is not open.");
+ 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);
+ }
+ }
}
void TileMap::_recreate_quadrants() {
+ // Clear then recreate all quadrants.
_clear_quadrants();
- for (Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) {
- PosKey qk = PosKey(E->key().x, E->key().y).to_quadrant(_get_quadrant_size());
+ for (Map<Vector2i, TileMapCell>::Element *E = tile_map.front(); E; E = E->next()) {
+ Vector2i qk = _coords_to_quadrant_coords(Vector2i(E->key().x, E->key().y));
- Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk);
+ Map<Vector2i, TileMapQuadrant>::Element *Q = quadrant_map.find(qk);
if (!Q) {
Q = _create_quadrant(qk);
- dirty_quadrant_list.add(&Q->get().dirty_list);
+ dirty_quadrant_list.add(&Q->get().dirty_list_element);
}
- Q->get().cells.insert(E->key());
+ Vector2i pk = E->key();
+ Q->get().cells.insert(pk);
+
_make_quadrant_dirty(Q, false);
}
+
update_dirty_quadrants();
}
void TileMap::_clear_quadrants() {
+ // Clear quadrants.
while (quadrant_map.size()) {
_erase_quadrant(quadrant_map.front());
}
-}
-void TileMap::set_material(const Ref<Material> &p_material) {
- CanvasItem::set_material(p_material);
- _update_all_items_material_state();
-}
-
-void TileMap::set_use_parent_material(bool p_use_parent_material) {
- CanvasItem::set_use_parent_material(p_use_parent_material);
- _update_all_items_material_state();
-}
-
-void TileMap::_update_all_items_material_state() {
- for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
- Quadrant &q = E->get();
- for (List<RID>::Element *F = q.canvas_items.front(); F; F = F->next()) {
- _update_item_material_state(F->get());
- }
+ // Clear the dirty quadrants list.
+ while (dirty_quadrant_list.first()) {
+ dirty_quadrant_list.remove(dirty_quadrant_list.first());
}
}
-void TileMap::_update_item_material_state(const RID &p_canvas_item) {
- RS::get_singleton()->canvas_item_set_use_parent_material(p_canvas_item, get_use_parent_material() || get_material().is_valid());
-}
-
void TileMap::clear() {
+ // Remove all tiles.
_clear_quadrants();
tile_map.clear();
used_size_cache_dirty = true;
}
void TileMap::_set_tile_data(const Vector<int> &p_data) {
- ERR_FAIL_COND(format > FORMAT_2);
+ // Set data for a given tile from raw data.
+ ERR_FAIL_COND(format > FORMAT_3);
int c = p_data.size();
const int *r = p_data.ptr();
- int offset = (format == FORMAT_2) ? 3 : 2;
+ int offset = (format >= FORMAT_2) ? 3 : 2;
clear();
for (int i = 0; i < c; i += offset) {
const uint8_t *ptr = (const uint8_t *)&r[i];
uint8_t local[12];
- for (int j = 0; j < ((format == FORMAT_2) ? 12 : 8); j++) {
+ for (int j = 0; j < ((format >= FORMAT_2) ? 12 : 8); j++) {
local[j] = ptr[j];
}
@@ -1163,31 +791,49 @@ void TileMap::_set_tile_data(const Vector<int> &p_data) {
SWAP(local[4], local[7]);
SWAP(local[5], local[6]);
//TODO: ask someone to check this...
- if (FORMAT == FORMAT_2) {
+ if (FORMAT >= FORMAT_2) {
SWAP(local[8], local[11]);
SWAP(local[9], local[10]);
}
#endif
+ int16_t x = decode_uint16(&local[0]);
+ int16_t y = decode_uint16(&local[2]);
+
+ if (format == FORMAT_3) {
+ uint16_t source_id = decode_uint16(&local[4]);
+ uint16_t atlas_coords_x = decode_uint16(&local[6]);
+ uint16_t atlas_coords_y = decode_uint32(&local[8]);
+ uint16_t alternative_tile = decode_uint16(&local[10]);
+ set_cell(Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile);
+ } else {
+ uint32_t v = decode_uint32(&local[4]);
+ v &= (1 << 29) - 1;
+
+ // We generate an alternative tile number out of the the flags
+ // An option should create the alternative in the tileset for compatibility
+ bool flip_h = v & (1 << 29);
+ bool flip_v = v & (1 << 30);
+ bool transpose = v & (1 << 31);
+ int16_t coord_x = 0;
+ int16_t coord_y = 0;
+ if (format == FORMAT_2) {
+ coord_x = decode_uint16(&local[8]);
+ coord_y = decode_uint16(&local[10]);
+ }
- uint16_t x = decode_uint16(&local[0]);
- uint16_t y = decode_uint16(&local[2]);
- uint32_t v = decode_uint32(&local[4]);
- bool flip_h = v & (1 << 29);
- bool flip_v = v & (1 << 30);
- bool transpose = v & (1 << 31);
- v &= (1 << 29) - 1;
- int16_t coord_x = 0;
- int16_t coord_y = 0;
- if (format == FORMAT_2) {
- coord_x = decode_uint16(&local[8]);
- coord_y = decode_uint16(&local[10]);
- }
+ int compatibility_alternative_tile = ((int)flip_h) + ((int)flip_v << 1) + ((int)transpose << 2);
- set_cell(x, y, v, flip_h, flip_v, transpose, Vector2(coord_x, coord_y));
+ if (tile_set.is_valid()) {
+ v = tile_set->compatibility_get_source_for_tile_id(v);
+ }
+
+ set_cell(Vector2i(x, y), v, Vector2i(coord_x, coord_y), compatibility_alternative_tile);
+ }
}
}
Vector<int> TileMap::_get_tile_data() const {
+ // Export tile data to raw format
Vector<int> data;
data.resize(tile_map.size() * 3);
int *w = data.ptrw();
@@ -1195,23 +841,14 @@ Vector<int> TileMap::_get_tile_data() const {
// Save in highest format
int idx = 0;
- for (const Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) {
+ for (const Map<Vector2i, TileMapCell>::Element *E = tile_map.front(); E; E = E->next()) {
uint8_t *ptr = (uint8_t *)&w[idx];
- encode_uint16(E->key().x, &ptr[0]);
- encode_uint16(E->key().y, &ptr[2]);
- uint32_t val = E->get().id;
- if (E->get().flip_h) {
- val |= (1 << 29);
- }
- if (E->get().flip_v) {
- val |= (1 << 30);
- }
- if (E->get().transpose) {
- val |= (1 << 31);
- }
- encode_uint32(val, &ptr[4]);
- encode_uint16(E->get().autotile_coord_x, &ptr[8]);
- encode_uint16(E->get().autotile_coord_y, &ptr[10]);
+ encode_uint16((int16_t)(E->key().x), &ptr[0]);
+ encode_uint16((int16_t)(E->key().y), &ptr[2]);
+ encode_uint16(E->get().source_id, &ptr[4]);
+ encode_uint16(E->get().coord_x, &ptr[6]);
+ encode_uint16(E->get().coord_y, &ptr[8]);
+ encode_uint16(E->get().alternative_tile, &ptr[10]);
idx += 3;
}
@@ -1220,6 +857,7 @@ Vector<int> TileMap::_get_tile_data() const {
#ifdef TOOLS_ENABLED
Rect2 TileMap::_edit_get_rect() const {
+ // Return the visible rect of the tilemap
if (pending_update) {
const_cast<TileMap *>(this)->update_dirty_quadrants();
} else {
@@ -1229,255 +867,6 @@ Rect2 TileMap::_edit_get_rect() const {
}
#endif
-void TileMap::set_collision_layer(uint32_t p_layer) {
- collision_layer = p_layer;
- if (!use_parent) {
- for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
- Quadrant &q = E->get();
- PhysicsServer2D::get_singleton()->body_set_collision_layer(q.body, collision_layer);
- }
- }
-}
-
-void TileMap::set_collision_mask(uint32_t p_mask) {
- collision_mask = p_mask;
- if (!use_parent) {
- for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
- Quadrant &q = E->get();
- PhysicsServer2D::get_singleton()->body_set_collision_mask(q.body, collision_mask);
- }
- }
-}
-
-void TileMap::set_collision_layer_bit(int p_bit, bool p_value) {
- ERR_FAIL_INDEX_MSG(p_bit, 32, "Collision layer bit must be between 0 and 31 inclusive.");
- uint32_t layer = get_collision_layer();
- if (p_value) {
- layer |= 1 << p_bit;
- } else {
- layer &= ~(1 << p_bit);
- }
- set_collision_layer(layer);
-}
-
-void TileMap::set_collision_mask_bit(int p_bit, bool p_value) {
- ERR_FAIL_INDEX_MSG(p_bit, 32, "Collision mask bit must be between 0 and 31 inclusive.");
- uint32_t mask = get_collision_mask();
- if (p_value) {
- mask |= 1 << p_bit;
- } else {
- mask &= ~(1 << p_bit);
- }
- set_collision_mask(mask);
-}
-
-bool TileMap::get_collision_use_kinematic() const {
- return use_kinematic;
-}
-
-void TileMap::set_collision_use_kinematic(bool p_use_kinematic) {
- _clear_quadrants();
- use_kinematic = p_use_kinematic;
- _recreate_quadrants();
-}
-
-bool TileMap::get_collision_use_parent() const {
- return use_parent;
-}
-
-void TileMap::set_collision_use_parent(bool p_use_parent) {
- if (use_parent == p_use_parent) {
- return;
- }
-
- _clear_quadrants();
-
- use_parent = p_use_parent;
- set_notify_local_transform(use_parent);
-
- if (use_parent && is_inside_tree()) {
- collision_parent = Object::cast_to<CollisionObject2D>(get_parent());
- } else {
- collision_parent = nullptr;
- }
-
- _recreate_quadrants();
- notify_property_list_changed();
- update_configuration_warnings();
-}
-
-void TileMap::set_collision_friction(float p_friction) {
- friction = p_friction;
- if (!use_parent) {
- for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
- Quadrant &q = E->get();
- PhysicsServer2D::get_singleton()->body_set_param(q.body, PhysicsServer2D::BODY_PARAM_FRICTION, p_friction);
- }
- }
-}
-
-float TileMap::get_collision_friction() const {
- return friction;
-}
-
-void TileMap::set_collision_bounce(float p_bounce) {
- bounce = p_bounce;
- if (!use_parent) {
- for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
- Quadrant &q = E->get();
- PhysicsServer2D::get_singleton()->body_set_param(q.body, PhysicsServer2D::BODY_PARAM_BOUNCE, p_bounce);
- }
- }
-}
-
-float TileMap::get_collision_bounce() const {
- return bounce;
-}
-
-void TileMap::set_bake_navigation(bool p_bake_navigation) {
- bake_navigation = p_bake_navigation;
- for (Map<PosKey, Quadrant>::Element *F = quadrant_map.front(); F; F = F->next()) {
- _make_quadrant_dirty(F);
- }
-}
-
-bool TileMap::is_baking_navigation() {
- return bake_navigation;
-}
-
-uint32_t TileMap::get_collision_layer() const {
- return collision_layer;
-}
-
-uint32_t TileMap::get_collision_mask() const {
- return collision_mask;
-}
-
-bool TileMap::get_collision_layer_bit(int p_bit) const {
- ERR_FAIL_INDEX_V_MSG(p_bit, 32, false, "Collision layer bit must be between 0 and 31 inclusive.");
- return get_collision_layer() & (1 << p_bit);
-}
-
-bool TileMap::get_collision_mask_bit(int p_bit) const {
- ERR_FAIL_INDEX_V_MSG(p_bit, 32, false, "Collision mask bit must be between 0 and 31 inclusive.");
- return get_collision_mask() & (1 << p_bit);
-}
-
-void TileMap::set_mode(Mode p_mode) {
- _clear_quadrants();
- mode = p_mode;
- _recreate_quadrants();
- emit_signal("settings_changed");
-}
-
-TileMap::Mode TileMap::get_mode() const {
- return mode;
-}
-
-void TileMap::set_half_offset(HalfOffset p_half_offset) {
- _clear_quadrants();
- half_offset = p_half_offset;
- _recreate_quadrants();
- emit_signal("settings_changed");
-}
-
-void TileMap::set_tile_origin(TileOrigin p_tile_origin) {
- _clear_quadrants();
- tile_origin = p_tile_origin;
- _recreate_quadrants();
- emit_signal("settings_changed");
-}
-
-TileMap::TileOrigin TileMap::get_tile_origin() const {
- return tile_origin;
-}
-
-Vector2 TileMap::get_cell_draw_offset() const {
- switch (mode) {
- case MODE_SQUARE: {
- return Vector2();
- } break;
- case MODE_ISOMETRIC: {
- return Vector2(-cell_size.x * 0.5, 0);
-
- } break;
- case MODE_CUSTOM: {
- Vector2 min;
- min.x = MIN(custom_transform[0].x, min.x);
- min.y = MIN(custom_transform[0].y, min.y);
- min.x = MIN(custom_transform[1].x, min.x);
- min.y = MIN(custom_transform[1].y, min.y);
- return min;
- } break;
- }
-
- return Vector2();
-}
-
-TileMap::HalfOffset TileMap::get_half_offset() const {
- return half_offset;
-}
-
-Transform2D TileMap::get_cell_transform() const {
- switch (mode) {
- case MODE_SQUARE: {
- Transform2D m;
- m[0] *= cell_size.x;
- m[1] *= cell_size.y;
- return m;
- } break;
- case MODE_ISOMETRIC: {
- //isometric only makes sense when y is positive in both x and y vectors, otherwise
- //the drawing of tiles will overlap
- Transform2D m;
- m[0] = Vector2(cell_size.x * 0.5, cell_size.y * 0.5);
- m[1] = Vector2(-cell_size.x * 0.5, cell_size.y * 0.5);
- return m;
-
- } break;
- case MODE_CUSTOM: {
- return custom_transform;
- } break;
- }
-
- return Transform2D();
-}
-
-void TileMap::set_custom_transform(const Transform2D &p_xform) {
- _clear_quadrants();
- custom_transform = p_xform;
- _recreate_quadrants();
- emit_signal("settings_changed");
-}
-
-Transform2D TileMap::get_custom_transform() const {
- return custom_transform;
-}
-
-Vector2 TileMap::_map_to_world(int p_x, int p_y, bool p_ignore_ofs) const {
- Vector2 ret = get_cell_transform().xform(Vector2(p_x, p_y));
- if (!p_ignore_ofs) {
- switch (half_offset) {
- case HALF_OFFSET_X:
- case HALF_OFFSET_NEGATIVE_X: {
- if (ABS(p_y) & 1) {
- ret += get_cell_transform()[0] * (half_offset == HALF_OFFSET_X ? 0.5 : -0.5);
- }
- } break;
- case HALF_OFFSET_Y:
- case HALF_OFFSET_NEGATIVE_Y: {
- if (ABS(p_x) & 1) {
- ret += get_cell_transform()[1] * (half_offset == HALF_OFFSET_Y ? 0.5 : -0.5);
- }
- } break;
- case HALF_OFFSET_DISABLED: {
- // Nothing to do.
- }
- }
- }
- return ret;
-}
-
bool TileMap::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == "format") {
if (p_value.get_type() == Variant::INT) {
@@ -1496,7 +885,7 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) {
bool TileMap::_get(const StringName &p_name, Variant &r_ret) const {
if (p_name == "format") {
- r_ret = FORMAT_2; // When saving, always save highest format
+ r_ret = FORMAT_3; // When saving, always save highest format
return true;
} else if (p_name == "tile_data") {
r_ret = _get_tile_data();
@@ -1513,93 +902,632 @@ void TileMap::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(p);
}
-void TileMap::_validate_property(PropertyInfo &property) const {
- if (use_parent && property.name != "collision_use_parent" && property.name.begins_with("collision_")) {
- property.usage = PROPERTY_USAGE_NOEDITOR;
+Vector2 TileMap::map_to_world(const Vector2i &p_pos) const {
+ // SHOULD RETURN THE CENTER OF THE TILE
+ ERR_FAIL_COND_V(!tile_set.is_valid(), Vector2());
+
+ Vector2 ret = p_pos;
+ TileSet::TileShape tile_shape = tile_set->get_tile_shape();
+ TileSet::TileOffsetAxis tile_offset_axis = tile_set->get_tile_offset_axis();
+
+ if (tile_shape == TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE || tile_shape == TileSet::TILE_SHAPE_HEXAGON || tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ // Technically, those 3 shapes are equivalent, as they are basically half-offset, but with different levels or overlap.
+ // square = no overlap, hexagon = 0.25 overlap, isometric = 0.5 overlap
+ if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ switch (tile_set->get_tile_layout()) {
+ case TileSet::TILE_LAYOUT_STACKED:
+ ret = Vector2(ret.x + (Math::posmod(ret.y, 2) == 0 ? 0.0 : 0.5), ret.y);
+ break;
+ case TileSet::TILE_LAYOUT_STACKED_OFFSET:
+ ret = Vector2(ret.x + (Math::posmod(ret.y, 2) == 1 ? 0.0 : 0.5), ret.y);
+ break;
+ case TileSet::TILE_LAYOUT_STAIRS_RIGHT:
+ ret = Vector2(ret.x + ret.y / 2, ret.y);
+ break;
+ case TileSet::TILE_LAYOUT_STAIRS_DOWN:
+ ret = Vector2(ret.x / 2, ret.y * 2 + ret.x);
+ break;
+ case TileSet::TILE_LAYOUT_DIAMOND_RIGHT:
+ ret = Vector2((ret.x + ret.y) / 2, ret.y - ret.x);
+ break;
+ case TileSet::TILE_LAYOUT_DIAMOND_DOWN:
+ ret = Vector2((ret.x - ret.y) / 2, ret.y + ret.x);
+ break;
+ }
+ } else { // TILE_OFFSET_AXIS_VERTICAL
+ switch (tile_set->get_tile_layout()) {
+ case TileSet::TILE_LAYOUT_STACKED:
+ ret = Vector2(ret.x, ret.y + (Math::posmod(ret.x, 2) == 0 ? 0.0 : 0.5));
+ break;
+ case TileSet::TILE_LAYOUT_STACKED_OFFSET:
+ ret = Vector2(ret.x, ret.y + (Math::posmod(ret.x, 2) == 1 ? 0.0 : 0.5));
+ break;
+ case TileSet::TILE_LAYOUT_STAIRS_RIGHT:
+ ret = Vector2(ret.x * 2 + ret.y, ret.y / 2);
+ break;
+ case TileSet::TILE_LAYOUT_STAIRS_DOWN:
+ ret = Vector2(ret.x, ret.y + ret.x / 2);
+ break;
+ case TileSet::TILE_LAYOUT_DIAMOND_RIGHT:
+ ret = Vector2(ret.x + ret.y, (ret.y - ret.x) / 2);
+ break;
+ case TileSet::TILE_LAYOUT_DIAMOND_DOWN:
+ ret = Vector2(ret.x - ret.y, (ret.y + ret.x) / 2);
+ break;
+ }
+ }
}
-}
-Vector2 TileMap::map_to_world(const Vector2 &p_pos, bool p_ignore_ofs) const {
- return _map_to_world(p_pos.x, p_pos.y, p_ignore_ofs);
+ // Multiply by the overlapping ratio
+ double overlapping_ratio = 1.0;
+ if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ overlapping_ratio = 0.5;
+ } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) {
+ overlapping_ratio = 0.75;
+ }
+ ret.y *= overlapping_ratio;
+ } else { // TILE_OFFSET_AXIS_VERTICAL
+ if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ overlapping_ratio = 0.5;
+ } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) {
+ overlapping_ratio = 0.75;
+ }
+ ret.x *= overlapping_ratio;
+ }
+
+ return (ret + Vector2(0.5, 0.5)) * tile_set->get_tile_size();
}
-Vector2 TileMap::world_to_map(const Vector2 &p_pos) const {
- Vector2 ret = get_cell_transform().affine_inverse().xform(p_pos);
+Vector2i TileMap::world_to_map(const Vector2 &p_pos) const {
+ ERR_FAIL_COND_V(!tile_set.is_valid(), Vector2i());
+
+ Vector2 ret = p_pos;
+ ret /= tile_set->get_tile_size();
- // Account for precision errors on the border (GH-23250).
- // 0.00005 is 5*CMP_EPSILON, results would start being unpredictable if
- // cell size is > 15,000, but we can hardly have more precision anyway with
- // floating point.
- ret += Vector2(0.00005, 0.00005);
+ TileSet::TileShape tile_shape = tile_set->get_tile_shape();
+ TileSet::TileOffsetAxis tile_offset_axis = tile_set->get_tile_offset_axis();
+ TileSet::TileLayout tile_layout = tile_set->get_tile_layout();
- switch (half_offset) {
- case HALF_OFFSET_X: {
- if (int(floor(ret.y)) & 1) {
- ret.x -= 0.5;
+ // Divide by the overlapping ratio
+ double overlapping_ratio = 1.0;
+ if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ overlapping_ratio = 0.5;
+ } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) {
+ overlapping_ratio = 0.75;
+ }
+ ret.y /= overlapping_ratio;
+ } else { // TILE_OFFSET_AXIS_VERTICAL
+ if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ overlapping_ratio = 0.5;
+ } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) {
+ overlapping_ratio = 0.75;
+ }
+ ret.x /= overlapping_ratio;
+ }
+
+ // For each half-offset shape, we check if we are in the corner of the tile, and thus should correct the world position accordingly.
+ if (tile_shape == TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE || tile_shape == TileSet::TILE_SHAPE_HEXAGON || tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ // Technically, those 3 shapes are equivalent, as they are basically half-offset, but with different levels or overlap.
+ // square = no overlap, hexagon = 0.25 overlap, isometric = 0.5 overlap
+ if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ // Smart floor of the position
+ Vector2 raw_pos = ret;
+ if (Math::posmod(Math::floor(ret.y), 2) ^ (tile_layout == TileSet::TILE_LAYOUT_STACKED_OFFSET)) {
+ ret = Vector2(Math::floor(ret.x + 0.5) - 0.5, Math::floor(ret.y));
+ } else {
+ ret = ret.floor();
}
- } break;
- case HALF_OFFSET_NEGATIVE_X: {
- if (int(floor(ret.y)) & 1) {
- ret.x += 0.5;
+
+ // Compute the tile offset, and if we might the output for a neighbour top tile
+ Vector2 in_tile_pos = raw_pos - ret;
+ bool in_top_left_triangle = (in_tile_pos - Vector2(0.5, 0.0)).cross(Vector2(-0.5, 1.0 / overlapping_ratio - 1)) <= 0;
+ bool in_top_right_triangle = (in_tile_pos - Vector2(0.5, 0.0)).cross(Vector2(0.5, 1.0 / overlapping_ratio - 1)) > 0;
+
+ switch (tile_layout) {
+ case TileSet::TILE_LAYOUT_STACKED:
+ ret = ret.floor();
+ if (in_top_left_triangle) {
+ ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 0 : -1, -1);
+ } else if (in_top_right_triangle) {
+ ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 1 : 0, -1);
+ }
+ break;
+ case TileSet::TILE_LAYOUT_STACKED_OFFSET:
+ ret = ret.floor();
+ if (in_top_left_triangle) {
+ ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? -1 : 0, -1);
+ } else if (in_top_right_triangle) {
+ ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 0 : 1, -1);
+ }
+ break;
+ case TileSet::TILE_LAYOUT_STAIRS_RIGHT:
+ ret = Vector2(ret.x - ret.y / 2, ret.y).floor();
+ if (in_top_left_triangle) {
+ ret += Vector2i(0, -1);
+ } else if (in_top_right_triangle) {
+ ret += Vector2i(1, -1);
+ }
+ break;
+ case TileSet::TILE_LAYOUT_STAIRS_DOWN:
+ ret = Vector2(ret.x * 2, ret.y / 2 - ret.x).floor();
+ if (in_top_left_triangle) {
+ ret += Vector2i(-1, 0);
+ } else if (in_top_right_triangle) {
+ ret += Vector2i(1, -1);
+ }
+ break;
+ case TileSet::TILE_LAYOUT_DIAMOND_RIGHT:
+ ret = Vector2(ret.x - ret.y / 2, ret.y / 2 + ret.x).floor();
+ if (in_top_left_triangle) {
+ ret += Vector2i(0, -1);
+ } else if (in_top_right_triangle) {
+ ret += Vector2i(1, 0);
+ }
+ break;
+ case TileSet::TILE_LAYOUT_DIAMOND_DOWN:
+ ret = Vector2(ret.x + ret.y / 2, ret.y / 2 - ret.x).floor();
+ if (in_top_left_triangle) {
+ ret += Vector2i(-1, 0);
+ } else if (in_top_right_triangle) {
+ ret += Vector2i(0, -1);
+ }
+ break;
}
- } break;
- case HALF_OFFSET_Y: {
- if (int(floor(ret.x)) & 1) {
- ret.y -= 0.5;
+ } else { // TILE_OFFSET_AXIS_VERTICAL
+ // Smart floor of the position
+ Vector2 raw_pos = ret;
+ if (Math::posmod(Math::floor(ret.x), 2) ^ (tile_layout == TileSet::TILE_LAYOUT_STACKED_OFFSET)) {
+ ret = Vector2(Math::floor(ret.x), Math::floor(ret.y + 0.5) - 0.5);
+ } else {
+ ret = ret.floor();
}
- } break;
- case HALF_OFFSET_NEGATIVE_Y: {
- if (int(floor(ret.x)) & 1) {
- ret.y += 0.5;
+
+ // Compute the tile offset, and if we might the output for a neighbour top tile
+ Vector2 in_tile_pos = raw_pos - ret;
+ bool in_top_left_triangle = (in_tile_pos - Vector2(0.0, 0.5)).cross(Vector2(1.0 / overlapping_ratio - 1, -0.5)) > 0;
+ bool in_bottom_left_triangle = (in_tile_pos - Vector2(0.0, 0.5)).cross(Vector2(1.0 / overlapping_ratio - 1, 0.5)) <= 0;
+
+ switch (tile_layout) {
+ case TileSet::TILE_LAYOUT_STACKED:
+ ret = ret.floor();
+ if (in_top_left_triangle) {
+ ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 0 : -1);
+ } else if (in_bottom_left_triangle) {
+ ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 1 : 0);
+ }
+ break;
+ case TileSet::TILE_LAYOUT_STACKED_OFFSET:
+ ret = ret.floor();
+ if (in_top_left_triangle) {
+ ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? -1 : 0);
+ } else if (in_bottom_left_triangle) {
+ ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 0 : 1);
+ }
+ break;
+ case TileSet::TILE_LAYOUT_STAIRS_RIGHT:
+ ret = Vector2(ret.x / 2 - ret.y, ret.y * 2).floor();
+ if (in_top_left_triangle) {
+ ret += Vector2i(0, -1);
+ } else if (in_bottom_left_triangle) {
+ ret += Vector2i(-1, 1);
+ }
+ break;
+ case TileSet::TILE_LAYOUT_STAIRS_DOWN:
+ ret = Vector2(ret.x, ret.y - ret.x / 2).floor();
+ if (in_top_left_triangle) {
+ ret += Vector2i(-1, 0);
+ } else if (in_bottom_left_triangle) {
+ ret += Vector2i(-1, 1);
+ }
+ break;
+ case TileSet::TILE_LAYOUT_DIAMOND_RIGHT:
+ ret = Vector2(ret.x / 2 - ret.y, ret.y + ret.x / 2).floor();
+ if (in_top_left_triangle) {
+ ret += Vector2i(0, -1);
+ } else if (in_bottom_left_triangle) {
+ ret += Vector2i(-1, 0);
+ }
+ break;
+ case TileSet::TILE_LAYOUT_DIAMOND_DOWN:
+ ret = Vector2(ret.x / 2 + ret.y, ret.y - ret.x / 2).floor();
+ if (in_top_left_triangle) {
+ ret += Vector2i(-1, 0);
+ } else if (in_bottom_left_triangle) {
+ ret += Vector2i(0, 1);
+ }
+ break;
}
- } break;
- case HALF_OFFSET_DISABLED: {
- // Nothing to do.
+ }
+ } else {
+ ret = (ret + Vector2(0.00005, 0.00005)).floor();
+ }
+ return Vector2i(ret);
+}
+
+bool TileMap::is_existing_neighbor(TileSet::CellNeighbor p_cell_neighbor) const {
+ ERR_FAIL_COND_V(!tile_set.is_valid(), false);
+
+ TileSet::TileShape shape = tile_set->get_tile_shape();
+ if (shape == TileSet::TILE_SHAPE_SQUARE) {
+ return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
+
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
+ } else {
+ if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
+ } else {
+ return p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
}
}
-
- return ret.floor();
}
-void TileMap::set_y_sort_enabled(bool p_enable) {
- _clear_quadrants();
- use_y_sort = p_enable;
- RS::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), use_y_sort);
- _recreate_quadrants();
- emit_signal("settings_changed");
-}
-
-bool TileMap::is_y_sort_enabled() const {
- return use_y_sort;
-}
+Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const {
+ ERR_FAIL_COND_V(!tile_set.is_valid(), p_coords);
+
+ TileSet::TileShape shape = tile_set->get_tile_shape();
+ if (shape == TileSet::TILE_SHAPE_SQUARE) {
+ switch (p_cell_neighbor) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_SIDE:
+ return p_coords + Vector2i(1, 0);
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ return p_coords + Vector2i(1, 1);
+ case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE:
+ return p_coords + Vector2i(0, 1);
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ return p_coords + Vector2i(-1, 1);
+ case TileSet::CELL_NEIGHBOR_LEFT_SIDE:
+ return p_coords + Vector2i(-1, 0);
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ return p_coords + Vector2i(-1, -1);
+ case TileSet::CELL_NEIGHBOR_TOP_SIDE:
+ return p_coords + Vector2i(0, -1);
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ return p_coords + Vector2i(1, -1);
+ default:
+ ERR_FAIL_V(p_coords);
+ }
+ } else { // Half-offset shapes (square and hexagon)
+ switch (tile_set->get_tile_layout()) {
+ case TileSet::TILE_LAYOUT_STACKED: {
+ if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ bool is_offset = p_coords.y % 2;
+ if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) {
+ return p_coords + Vector2i(1, 0);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) {
+ return p_coords + Vector2i(is_offset ? 1 : 0, 1);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) {
+ return p_coords + Vector2i(0, 2);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
+ return p_coords + Vector2i(is_offset ? 0 : -1, 1);
+ } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
+ return p_coords + Vector2i(-1, 0);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
+ return p_coords + Vector2i(is_offset ? 0 : -1, -1);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) {
+ return p_coords + Vector2i(0, -2);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
+ return p_coords + Vector2i(is_offset ? 1 : 0, -1);
+ } else {
+ ERR_FAIL_V(p_coords);
+ }
+ } else {
+ bool is_offset = p_coords.x % 2;
+
+ if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) {
+ return p_coords + Vector2i(0, 1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) {
+ return p_coords + Vector2i(1, is_offset ? 1 : 0);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) {
+ return p_coords + Vector2i(2, 0);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
+ return p_coords + Vector2i(1, is_offset ? 0 : -1);
+ } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
+ return p_coords + Vector2i(0, -1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
+ return p_coords + Vector2i(-1, is_offset ? 0 : -1);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) {
+ return p_coords + Vector2i(-2, 0);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
+ return p_coords + Vector2i(-1, is_offset ? 1 : 0);
+ } else {
+ ERR_FAIL_V(p_coords);
+ }
+ }
+ } break;
+ case TileSet::TILE_LAYOUT_STACKED_OFFSET: {
+ if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ bool is_offset = p_coords.y % 2;
+
+ if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) {
+ return p_coords + Vector2i(1, 0);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) {
+ return p_coords + Vector2i(is_offset ? 0 : 1, 1);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) {
+ return p_coords + Vector2i(0, 2);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
+ return p_coords + Vector2i(is_offset ? -1 : 0, 1);
+ } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
+ return p_coords + Vector2i(-1, 0);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
+ return p_coords + Vector2i(is_offset ? -1 : 0, -1);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) {
+ return p_coords + Vector2i(0, -2);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
+ return p_coords + Vector2i(is_offset ? 0 : 1, -1);
+ } else {
+ ERR_FAIL_V(p_coords);
+ }
+ } else {
+ bool is_offset = p_coords.x % 2;
+
+ if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) {
+ return p_coords + Vector2i(0, 1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) {
+ return p_coords + Vector2i(1, is_offset ? 0 : 1);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) {
+ return p_coords + Vector2i(2, 0);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
+ return p_coords + Vector2i(1, is_offset ? -1 : 0);
+ } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
+ return p_coords + Vector2i(0, -1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
+ return p_coords + Vector2i(-1, is_offset ? -1 : 0);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) {
+ return p_coords + Vector2i(-2, 0);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
+ return p_coords + Vector2i(-1, is_offset ? 0 : 1);
+ } else {
+ ERR_FAIL_V(p_coords);
+ }
+ }
+ } break;
+ case TileSet::TILE_LAYOUT_STAIRS_RIGHT:
+ case TileSet::TILE_LAYOUT_STAIRS_DOWN: {
+ if ((tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL)) {
+ if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) {
+ return p_coords + Vector2i(1, 0);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) {
+ return p_coords + Vector2i(0, 1);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) {
+ return p_coords + Vector2i(-1, 2);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
+ return p_coords + Vector2i(-1, 1);
+ } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
+ return p_coords + Vector2i(-1, 0);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
+ return p_coords + Vector2i(0, -1);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) {
+ return p_coords + Vector2i(1, -2);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
+ return p_coords + Vector2i(1, -1);
+ } else {
+ ERR_FAIL_V(p_coords);
+ }
-void TileMap::set_compatibility_mode(bool p_enable) {
- _clear_quadrants();
- compatibility_mode = p_enable;
- _recreate_quadrants();
- emit_signal("settings_changed");
-}
+ } else {
+ if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) {
+ return p_coords + Vector2i(0, 1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) {
+ return p_coords + Vector2i(1, 0);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) {
+ return p_coords + Vector2i(2, -1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
+ return p_coords + Vector2i(1, -1);
+ } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
+ return p_coords + Vector2i(0, -1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
+ return p_coords + Vector2i(-1, 0);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) {
+ return p_coords + Vector2i(-2, 1);
+
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
+ return p_coords + Vector2i(-1, 1);
+ } else {
+ ERR_FAIL_V(p_coords);
+ }
+ }
+ } else {
+ if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) {
+ return p_coords + Vector2i(2, -1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) {
+ return p_coords + Vector2i(1, 0);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) {
+ return p_coords + Vector2i(0, 1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
+ return p_coords + Vector2i(-1, 1);
+ } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
+ return p_coords + Vector2i(-2, 1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
+ return p_coords + Vector2i(-1, 0);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) {
+ return p_coords + Vector2i(0, -1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
+ return p_coords + Vector2i(1, -1);
+ } else {
+ ERR_FAIL_V(p_coords);
+ }
-bool TileMap::is_compatibility_mode_enabled() const {
- return compatibility_mode;
-}
+ } else {
+ if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) {
+ return p_coords + Vector2i(-1, 2);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) {
+ return p_coords + Vector2i(0, 1);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) {
+ return p_coords + Vector2i(1, 0);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
+ return p_coords + Vector2i(1, -1);
+ } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
+ return p_coords + Vector2i(1, -2);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
+ return p_coords + Vector2i(0, -1);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) {
+ return p_coords + Vector2i(-1, 0);
+
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
+ return p_coords + Vector2i(-1, 1);
+ } else {
+ ERR_FAIL_V(p_coords);
+ }
+ }
+ }
+ } break;
+ case TileSet::TILE_LAYOUT_DIAMOND_RIGHT:
+ case TileSet::TILE_LAYOUT_DIAMOND_DOWN: {
+ if ((tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL)) {
+ if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) {
+ return p_coords + Vector2i(1, 1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) {
+ return p_coords + Vector2i(0, 1);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) {
+ return p_coords + Vector2i(-1, 1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
+ return p_coords + Vector2i(-1, 0);
+ } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
+ return p_coords + Vector2i(-1, -1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
+ return p_coords + Vector2i(0, -1);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) {
+ return p_coords + Vector2i(1, -1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
+ return p_coords + Vector2i(1, 0);
+ } else {
+ ERR_FAIL_V(p_coords);
+ }
-void TileMap::set_centered_textures(bool p_enable) {
- _clear_quadrants();
- centered_textures = p_enable;
- _recreate_quadrants();
- emit_signal("settings_changed");
-}
+ } else {
+ if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) {
+ return p_coords + Vector2i(1, 1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) {
+ return p_coords + Vector2i(1, 0);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) {
+ return p_coords + Vector2i(1, -1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
+ return p_coords + Vector2i(0, -1);
+ } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
+ return p_coords + Vector2i(-1, -1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
+ return p_coords + Vector2i(-1, 0);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) {
+ return p_coords + Vector2i(-1, 1);
+
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
+ return p_coords + Vector2i(0, 1);
+ } else {
+ ERR_FAIL_V(p_coords);
+ }
+ }
+ } else {
+ if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) {
+ return p_coords + Vector2i(1, -1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) {
+ return p_coords + Vector2i(1, 0);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) {
+ return p_coords + Vector2i(1, 1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
+ return p_coords + Vector2i(0, 1);
+ } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
+ return p_coords + Vector2i(-1, 1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
+ return p_coords + Vector2i(-1, 0);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) {
+ return p_coords + Vector2i(-1, -1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
+ return p_coords + Vector2i(0, -1);
+ } else {
+ ERR_FAIL_V(p_coords);
+ }
-bool TileMap::is_centered_textures_enabled() const {
- return centered_textures;
+ } else {
+ if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) {
+ return p_coords + Vector2i(-1, 1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) {
+ return p_coords + Vector2i(0, 1);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) {
+ return p_coords + Vector2i(1, 1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
+ return p_coords + Vector2i(1, 0);
+ } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) ||
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
+ return p_coords + Vector2i(1, -1);
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
+ return p_coords + Vector2i(0, -1);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) {
+ return p_coords + Vector2i(-1, -1);
+
+ } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
+ return p_coords + Vector2i(-1, 0);
+ } else {
+ ERR_FAIL_V(p_coords);
+ }
+ }
+ }
+ } break;
+ default:
+ ERR_FAIL_V(p_coords);
+ }
+ }
}
TypedArray<Vector2i> TileMap::get_used_cells() const {
+ // Returns the cells used in the tilemap.
TypedArray<Vector2i> a;
a.resize(tile_map.size());
int i = 0;
- for (Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) {
+ for (Map<Vector2i, TileMapCell>::Element *E = tile_map.front(); E; E = E->next()) {
Vector2i p(E->key().x, E->key().y);
a[i++] = p;
}
@@ -1607,25 +1535,13 @@ TypedArray<Vector2i> TileMap::get_used_cells() const {
return a;
}
-TypedArray<Vector2i> TileMap::get_used_cells_by_index(int p_id) const {
- TypedArray<Vector2i> a;
- for (Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) {
- if (E->value().id == p_id) {
- Vector2i p(E->key().x, E->key().y);
- a.push_back(p);
- }
- }
-
- return a;
-}
-
Rect2 TileMap::get_used_rect() { // Not const because of cache
-
+ // Return the rect of the currently used area
if (used_size_cache_dirty) {
if (tile_map.size() > 0) {
used_size_cache = Rect2(tile_map.front()->key().x, tile_map.front()->key().y, 0, 0);
- for (Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) {
+ for (Map<Vector2i, TileMapCell>::Element *E = tile_map.front(); E; E = E->next()) {
used_size_cache.expand_to(Vector2(E->key().x, E->key().y));
}
@@ -1640,46 +1556,49 @@ Rect2 TileMap::get_used_rect() { // Not const because of cache
return used_size_cache;
}
-void TileMap::set_occluder_light_mask(int p_mask) {
- occluder_light_mask = p_mask;
- for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
- for (Map<PosKey, Quadrant::Occluder>::Element *F = E->get().occluder_instances.front(); F; F = F->next()) {
- RenderingServer::get_singleton()->canvas_light_occluder_set_light_mask(F->get().id, occluder_light_mask);
- }
- }
-}
-
-int TileMap::get_occluder_light_mask() const {
- return occluder_light_mask;
-}
+// --- Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems ---
void TileMap::set_light_mask(int p_light_mask) {
+ // Occlusion: set light mask.
CanvasItem::set_light_mask(p_light_mask);
- for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
+ for (Map<Vector2i, TileMapQuadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
for (List<RID>::Element *F = E->get().canvas_items.front(); F; F = F->next()) {
RenderingServer::get_singleton()->canvas_item_set_light_mask(F->get(), get_light_mask());
}
}
}
-void TileMap::set_clip_uv(bool p_enable) {
- if (clip_uv == p_enable) {
- return;
- }
+void TileMap::set_material(const Ref<Material> &p_material) {
+ // Set material for the whole tilemap.
+ CanvasItem::set_material(p_material);
- _clear_quadrants();
- clip_uv = p_enable;
- _recreate_quadrants();
+ // Update material for the whole tilemap.
+ for (Map<Vector2i, TileMapQuadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
+ TileMapQuadrant &q = E->get();
+ for (List<RID>::Element *F = q.canvas_items.front(); F; F = F->next()) {
+ RS::get_singleton()->canvas_item_set_use_parent_material(F->get(), get_use_parent_material() || get_material().is_valid());
+ }
+ }
}
-bool TileMap::get_clip_uv() const {
- return clip_uv;
+void TileMap::set_use_parent_material(bool p_use_parent_material) {
+ // Set use_parent_material for the whole tilemap.
+ CanvasItem::set_use_parent_material(p_use_parent_material);
+
+ // Update use_parent_material for the whole tilemap.
+ for (Map<Vector2i, TileMapQuadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
+ TileMapQuadrant &q = E->get();
+ for (List<RID>::Element *F = q.canvas_items.front(); F; F = F->next()) {
+ RS::get_singleton()->canvas_item_set_use_parent_material(F->get(), get_use_parent_material() || get_material().is_valid());
+ }
+ }
}
void TileMap::set_texture_filter(TextureFilter p_texture_filter) {
+ // Set a default texture filter for the whole tilemap
CanvasItem::set_texture_filter(p_texture_filter);
- for (Map<PosKey, Quadrant>::Element *F = quadrant_map.front(); F; F = F->next()) {
- Quadrant &q = F->get();
+ for (Map<Vector2i, TileMapQuadrant>::Element *F = quadrant_map.front(); F; F = F->next()) {
+ TileMapQuadrant &q = F->get();
for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) {
RenderingServer::get_singleton()->canvas_item_set_default_texture_filter(E->get(), RS::CanvasItemTextureFilter(p_texture_filter));
_make_quadrant_dirty(F);
@@ -1688,9 +1607,10 @@ void TileMap::set_texture_filter(TextureFilter p_texture_filter) {
}
void TileMap::set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) {
+ // Set a default texture repeat for the whole tilemap
CanvasItem::set_texture_repeat(p_texture_repeat);
- for (Map<PosKey, Quadrant>::Element *F = quadrant_map.front(); F; F = F->next()) {
- Quadrant &q = F->get();
+ for (Map<Vector2i, TileMapQuadrant>::Element *F = quadrant_map.front(); F; F = F->next()) {
+ TileMapQuadrant &q = F->get();
for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) {
RenderingServer::get_singleton()->canvas_item_set_default_texture_repeat(E->get(), RS::CanvasItemTextureRepeat(p_texture_repeat));
_make_quadrant_dirty(F);
@@ -1698,167 +1618,153 @@ void TileMap::set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) {
}
}
-TypedArray<String> TileMap::get_configuration_warnings() const {
- TypedArray<String> warnings = Node::get_configuration_warnings();
+TypedArray<Vector2i> TileMap::get_surrounding_tiles(Vector2i coords) {
+ if (!tile_set.is_valid()) {
+ return TypedArray<Vector2i>();
+ }
- if (use_parent && !collision_parent) {
- warnings.push_back(TTR("TileMap with Use Parent on needs a parent CollisionObject2D to give shapes to. Please use it as a child of Area2D, StaticBody2D, RigidBody2D, KinematicBody2D, etc. to give them a shape."));
+ TypedArray<Vector2i> around;
+ TileSet::TileShape shape = tile_set->get_tile_shape();
+ if (shape == TileSet::TILE_SHAPE_SQUARE) {
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE));
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE));
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE));
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_SIDE));
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE));
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE));
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE));
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE));
+ } else {
+ if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE));
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE));
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE));
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE));
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE));
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE));
+ } else {
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE));
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE));
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE));
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE));
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_SIDE));
+ around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE));
+ }
}
- return warnings;
+ return around;
}
-void TileMap::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_tileset", "tileset"), &TileMap::set_tileset);
- ClassDB::bind_method(D_METHOD("get_tileset"), &TileMap::get_tileset);
-
- ClassDB::bind_method(D_METHOD("set_mode", "mode"), &TileMap::set_mode);
- ClassDB::bind_method(D_METHOD("get_mode"), &TileMap::get_mode);
+void TileMap::draw_cells_outline(Control *p_control, Set<Vector2i> p_cells, Color p_color, Transform2D p_transform) {
+ if (!tile_set.is_valid()) {
+ return;
+ }
- ClassDB::bind_method(D_METHOD("set_half_offset", "half_offset"), &TileMap::set_half_offset);
- ClassDB::bind_method(D_METHOD("get_half_offset"), &TileMap::get_half_offset);
+ // Create a set.
+ Vector2i tile_size = tile_set->get_tile_size();
+ Vector<Vector2> uvs;
- ClassDB::bind_method(D_METHOD("set_custom_transform", "custom_transform"), &TileMap::set_custom_transform);
- ClassDB::bind_method(D_METHOD("get_custom_transform"), &TileMap::get_custom_transform);
+ if (tile_set->get_tile_shape() == TileSet::TILE_SHAPE_SQUARE) {
+ uvs.append(Vector2(1.0, 0.0));
+ uvs.append(Vector2(1.0, 1.0));
+ uvs.append(Vector2(0.0, 1.0));
+ uvs.append(Vector2(0.0, 0.0));
+ } else {
+ float overlap = 0.0;
+ switch (tile_set->get_tile_shape()) {
+ case TileSet::TILE_SHAPE_ISOMETRIC:
+ overlap = 0.5;
+ break;
+ case TileSet::TILE_SHAPE_HEXAGON:
+ overlap = 0.25;
+ break;
+ case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE:
+ overlap = 0.0;
+ break;
+ default:
+ break;
+ }
+ uvs.append(Vector2(1.0, overlap));
+ uvs.append(Vector2(1.0, 1.0 - overlap));
+ uvs.append(Vector2(0.5, 1.0));
+ uvs.append(Vector2(0.0, 1.0 - overlap));
+ uvs.append(Vector2(0.0, overlap));
+ uvs.append(Vector2(0.5, 0.0));
+ if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL) {
+ for (int i = 0; i < uvs.size(); i++) {
+ uvs.write[i] = Vector2(uvs[i].y, uvs[i].x);
+ }
+ }
+ }
- ClassDB::bind_method(D_METHOD("set_cell_size", "size"), &TileMap::set_cell_size);
- ClassDB::bind_method(D_METHOD("get_cell_size"), &TileMap::get_cell_size);
+ for (Set<Vector2i>::Element *E = p_cells.front(); E; E = E->next()) {
+ Vector2 top_left = map_to_world(E->get()) - tile_size / 2;
+ TypedArray<Vector2i> surrounding_tiles = get_surrounding_tiles(E->get());
+ for (int i = 0; i < surrounding_tiles.size(); i++) {
+ if (!p_cells.has(surrounding_tiles[i])) {
+ p_control->draw_line(p_transform.xform(top_left + uvs[i] * tile_size), p_transform.xform(top_left + uvs[(i + 1) % uvs.size()] * tile_size), p_color);
+ }
+ }
+ }
+}
- ClassDB::bind_method(D_METHOD("_set_old_cell_size", "size"), &TileMap::_set_old_cell_size);
- ClassDB::bind_method(D_METHOD("_get_old_cell_size"), &TileMap::_get_old_cell_size);
+void TileMap::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_tileset", "tileset"), &TileMap::set_tileset);
+ ClassDB::bind_method(D_METHOD("get_tileset"), &TileMap::get_tileset);
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_tile_origin", "origin"), &TileMap::set_tile_origin);
- ClassDB::bind_method(D_METHOD("get_tile_origin"), &TileMap::get_tile_origin);
-
- ClassDB::bind_method(D_METHOD("set_clip_uv", "enable"), &TileMap::set_clip_uv);
- ClassDB::bind_method(D_METHOD("get_clip_uv"), &TileMap::get_clip_uv);
-
- ClassDB::bind_method(D_METHOD("set_y_sort_enabled", "enable"), &TileMap::set_y_sort_enabled);
- ClassDB::bind_method(D_METHOD("is_y_sort_enabled"), &TileMap::is_y_sort_enabled);
-
- ClassDB::bind_method(D_METHOD("set_compatibility_mode", "enable"), &TileMap::set_compatibility_mode);
- ClassDB::bind_method(D_METHOD("is_compatibility_mode_enabled"), &TileMap::is_compatibility_mode_enabled);
-
- ClassDB::bind_method(D_METHOD("set_centered_textures", "enable"), &TileMap::set_centered_textures);
- ClassDB::bind_method(D_METHOD("is_centered_textures_enabled"), &TileMap::is_centered_textures_enabled);
-
- ClassDB::bind_method(D_METHOD("set_collision_use_kinematic", "use_kinematic"), &TileMap::set_collision_use_kinematic);
- ClassDB::bind_method(D_METHOD("get_collision_use_kinematic"), &TileMap::get_collision_use_kinematic);
-
- ClassDB::bind_method(D_METHOD("set_collision_use_parent", "use_parent"), &TileMap::set_collision_use_parent);
- ClassDB::bind_method(D_METHOD("get_collision_use_parent"), &TileMap::get_collision_use_parent);
-
- ClassDB::bind_method(D_METHOD("set_collision_layer", "layer"), &TileMap::set_collision_layer);
- ClassDB::bind_method(D_METHOD("get_collision_layer"), &TileMap::get_collision_layer);
-
- ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &TileMap::set_collision_mask);
- ClassDB::bind_method(D_METHOD("get_collision_mask"), &TileMap::get_collision_mask);
-
- ClassDB::bind_method(D_METHOD("set_collision_layer_bit", "bit", "value"), &TileMap::set_collision_layer_bit);
- ClassDB::bind_method(D_METHOD("get_collision_layer_bit", "bit"), &TileMap::get_collision_layer_bit);
-
- ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &TileMap::set_collision_mask_bit);
- ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &TileMap::get_collision_mask_bit);
-
- ClassDB::bind_method(D_METHOD("set_collision_friction", "value"), &TileMap::set_collision_friction);
- ClassDB::bind_method(D_METHOD("get_collision_friction"), &TileMap::get_collision_friction);
-
- ClassDB::bind_method(D_METHOD("set_collision_bounce", "value"), &TileMap::set_collision_bounce);
- ClassDB::bind_method(D_METHOD("get_collision_bounce"), &TileMap::get_collision_bounce);
-
- ClassDB::bind_method(D_METHOD("set_bake_navigation", "bake_navigation"), &TileMap::set_bake_navigation);
- ClassDB::bind_method(D_METHOD("is_baking_navigation"), &TileMap::is_baking_navigation);
-
- ClassDB::bind_method(D_METHOD("set_occluder_light_mask", "mask"), &TileMap::set_occluder_light_mask);
- ClassDB::bind_method(D_METHOD("get_occluder_light_mask"), &TileMap::get_occluder_light_mask);
-
- ClassDB::bind_method(D_METHOD("set_cell", "x", "y", "tile", "flip_x", "flip_y", "transpose", "autotile_coord"), &TileMap::set_cell, DEFVAL(false), DEFVAL(false), DEFVAL(false), DEFVAL(Vector2()));
- ClassDB::bind_method(D_METHOD("set_cellv", "position", "tile", "flip_x", "flip_y", "transpose"), &TileMap::set_cellv, DEFVAL(false), DEFVAL(false), DEFVAL(false));
- ClassDB::bind_method(D_METHOD("_set_celld", "position", "data"), &TileMap::_set_celld);
- ClassDB::bind_method(D_METHOD("get_cell", "x", "y"), &TileMap::get_cell);
- ClassDB::bind_method(D_METHOD("get_cellv", "position"), &TileMap::get_cellv);
- ClassDB::bind_method(D_METHOD("is_cell_x_flipped", "x", "y"), &TileMap::is_cell_x_flipped);
- ClassDB::bind_method(D_METHOD("is_cell_y_flipped", "x", "y"), &TileMap::is_cell_y_flipped);
- ClassDB::bind_method(D_METHOD("is_cell_transposed", "x", "y"), &TileMap::is_cell_transposed);
-
- ClassDB::bind_method(D_METHOD("get_cell_autotile_coord", "x", "y"), &TileMap::get_cell_autotile_coord);
+ 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("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);
ClassDB::bind_method(D_METHOD("fix_invalid_tiles"), &TileMap::fix_invalid_tiles);
+ ClassDB::bind_method(D_METHOD("get_surrounding_tiles", "coords"), &TileMap::get_surrounding_tiles);
ClassDB::bind_method(D_METHOD("clear"), &TileMap::clear);
ClassDB::bind_method(D_METHOD("get_used_cells"), &TileMap::get_used_cells);
- ClassDB::bind_method(D_METHOD("get_used_cells_by_index", "index"), &TileMap::get_used_cells_by_index);
ClassDB::bind_method(D_METHOD("get_used_rect"), &TileMap::get_used_rect);
- ClassDB::bind_method(D_METHOD("map_to_world", "map_position", "ignore_half_ofs"), &TileMap::map_to_world, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("map_to_world", "map_position"), &TileMap::map_to_world);
ClassDB::bind_method(D_METHOD("world_to_map", "world_position"), &TileMap::world_to_map);
- ClassDB::bind_method(D_METHOD("_clear_quadrants"), &TileMap::_clear_quadrants);
- ClassDB::bind_method(D_METHOD("update_dirty_quadrants"), &TileMap::update_dirty_quadrants);
+ ClassDB::bind_method(D_METHOD("get_neighbor_cell", "coords", "neighbor"), &TileMap::get_neighbor_cell);
- ClassDB::bind_method(D_METHOD("update_bitmask_area", "position"), &TileMap::update_bitmask_area);
- ClassDB::bind_method(D_METHOD("update_bitmask_region", "start", "end"), &TileMap::update_bitmask_region, DEFVAL(Vector2()), DEFVAL(Vector2()));
+ ClassDB::bind_method(D_METHOD("update_dirty_quadrants"), &TileMap::update_dirty_quadrants);
ClassDB::bind_method(D_METHOD("_set_tile_data"), &TileMap::_set_tile_data);
ClassDB::bind_method(D_METHOD("_get_tile_data"), &TileMap::_get_tile_data);
- ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Square,Isometric,Custom"), "set_mode", "get_mode");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile_set", PROPERTY_HINT_RESOURCE_TYPE, "TileSet"), "set_tileset", "get_tileset");
-
- ADD_GROUP("Cell", "cell_");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "cell_size", PROPERTY_HINT_RANGE, "1,8192,1"), "set_cell_size", "get_cell_size");
ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_quadrant_size", PROPERTY_HINT_RANGE, "1,128,1"), "set_quadrant_size", "get_quadrant_size");
- ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "cell_custom_transform"), "set_custom_transform", "get_custom_transform");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_half_offset", PROPERTY_HINT_ENUM, "Offset X,Offset Y,Disabled,Offset Negative X,Offset Negative Y"), "set_half_offset", "get_half_offset");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_tile_origin", PROPERTY_HINT_ENUM, "Top Left,Center,Bottom Left"), "set_tile_origin", "get_tile_origin");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cell_y_sort"), "set_y_sort_enabled", "is_y_sort_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "compatibility_mode"), "set_compatibility_mode", "is_compatibility_mode_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "centered_textures"), "set_centered_textures", "is_centered_textures_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cell_clip_uv"), "set_clip_uv", "get_clip_uv");
-
- ADD_GROUP("Collision", "collision_");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_use_parent", PROPERTY_HINT_NONE, ""), "set_collision_use_parent", "get_collision_use_parent");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_use_kinematic", PROPERTY_HINT_NONE, ""), "set_collision_use_kinematic", "get_collision_use_kinematic");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_friction", "get_collision_friction");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_bounce", "get_collision_bounce");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_layer", "get_collision_layer");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask");
-
- ADD_GROUP("Occluder", "occluder_");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "occluder_light_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_occluder_light_mask", "get_occluder_light_mask");
-
- ADD_GROUP("Navigation", "");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bake_navigation"), "set_bake_navigation", "is_baking_navigation");
ADD_PROPERTY_DEFAULT("format", FORMAT_1);
- ADD_SIGNAL(MethodInfo("settings_changed"));
-
- BIND_CONSTANT(INVALID_CELL);
-
- BIND_ENUM_CONSTANT(MODE_SQUARE);
- BIND_ENUM_CONSTANT(MODE_ISOMETRIC);
- BIND_ENUM_CONSTANT(MODE_CUSTOM);
-
- BIND_ENUM_CONSTANT(HALF_OFFSET_X);
- BIND_ENUM_CONSTANT(HALF_OFFSET_Y);
- BIND_ENUM_CONSTANT(HALF_OFFSET_DISABLED);
- BIND_ENUM_CONSTANT(HALF_OFFSET_NEGATIVE_X);
- BIND_ENUM_CONSTANT(HALF_OFFSET_NEGATIVE_Y);
+ ADD_SIGNAL(MethodInfo("changed"));
+}
- BIND_ENUM_CONSTANT(TILE_ORIGIN_TOP_LEFT);
- BIND_ENUM_CONSTANT(TILE_ORIGIN_CENTER);
- BIND_ENUM_CONSTANT(TILE_ORIGIN_BOTTOM_LEFT);
+void TileMap::_tile_set_changed() {
+ emit_signal("changed");
+ _make_all_quadrants_dirty(true);
}
TileMap::TileMap() {
+ rect_cache_dirty = true;
+ used_size_cache_dirty = true;
+ pending_update = false;
+ quadrant_size = 16;
+ format = FORMAT_1; // Assume lowest possible format if none is present
+
set_notify_transform(true);
set_notify_local_transform(false);
}
TileMap::~TileMap() {
- clear();
+ if (tile_set.is_valid()) {
+ tile_set->disconnect("changed", callable_mp(this, &TileMap::_tile_set_changed));
+ }
+ _clear_quadrants();
}
diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h
index 9d27053fee..e9dbccbdb9 100644
--- a/scene/2d/tile_map.h
+++ b/scene/2d/tile_map.h
@@ -34,193 +34,194 @@
#include "core/templates/self_list.h"
#include "core/templates/vset.h"
#include "scene/2d/node_2d.h"
+#include "scene/gui/control.h"
#include "scene/resources/tile_set.h"
-class CollisionObject2D;
+class TileSetAtlasSource;
-class TileMap : public Node2D {
- GDCLASS(TileMap, Node2D);
-
-public:
- enum Mode {
- MODE_SQUARE,
- MODE_ISOMETRIC,
- MODE_CUSTOM
+union TileMapCell {
+ struct {
+ int32_t source_id : 16;
+ int16_t coord_x : 16;
+ int16_t coord_y : 16;
+ int32_t alternative_tile : 16;
};
- enum HalfOffset {
- HALF_OFFSET_X,
- HALF_OFFSET_Y,
- HALF_OFFSET_DISABLED,
- HALF_OFFSET_NEGATIVE_X,
- HALF_OFFSET_NEGATIVE_Y,
- };
+ 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) {
+ source_id = p_source_id;
+ set_atlas_coords(p_atlas_coords);
+ alternative_tile = p_alternative_tile;
+ }
+
+ Vector2i get_atlas_coords() const {
+ return Vector2i(coord_x, coord_y);
+ }
+
+ void set_atlas_coords(const Vector2i &r_coords) {
+ coord_x = r_coords.x;
+ coord_y = r_coords.y;
+ }
+
+ bool operator<(const TileMapCell &p_other) const {
+ if (source_id == p_other.source_id) {
+ if (coord_x == p_other.coord_x) {
+ if (coord_y == p_other.coord_y) {
+ return alternative_tile < p_other.alternative_tile;
+ } else {
+ return coord_y < p_other.coord_y;
+ }
+ } else {
+ return coord_x < p_other.coord_x;
+ }
+ } else {
+ return source_id < p_other.source_id;
+ }
+ }
- enum TileOrigin {
- TILE_ORIGIN_TOP_LEFT,
- TILE_ORIGIN_CENTER,
- TILE_ORIGIN_BOTTOM_LEFT
- };
+ bool operator!=(const TileMapCell &p_other) const {
+ return !(source_id == p_other.source_id && coord_x == p_other.coord_x && coord_y == p_other.coord_y && alternative_tile == p_other.alternative_tile);
+ }
+};
-private:
- enum DataFormat {
- FORMAT_1 = 0,
- FORMAT_2
+struct TileMapQuadrant {
+ struct CoordsWorldComparator {
+ _ALWAYS_INLINE_ bool operator()(const Vector2i &p_a, const Vector2i &p_b) const {
+ // We sort the cells by their world coords, as it is needed by rendering.
+ if (p_a.y == p_b.y) {
+ return p_a.x > p_b.x;
+ } else {
+ return p_a.y < p_b.y;
+ }
+ }
};
- Ref<TileSet> tile_set;
- Size2i cell_size = Size2(64, 64);
- int quadrant_size = 16;
- Mode mode = MODE_SQUARE;
- Transform2D custom_transform = Transform2D(64, 0, 0, 64, 0, 0);
- HalfOffset half_offset = HALF_OFFSET_DISABLED;
- bool use_parent = false;
- CollisionObject2D *collision_parent = nullptr;
- bool use_kinematic = false;
- bool bake_navigation = false;
-
- union PosKey {
- struct {
- int16_t x;
- int16_t y;
- };
- uint32_t key = 0;
-
- //using a more precise comparison so the regions can be sorted later
- bool operator<(const PosKey &p_k) const { return (y == p_k.y) ? x < p_k.x : y < p_k.y; }
-
- bool operator==(const PosKey &p_k) const { return (y == p_k.y && x == p_k.x); }
-
- PosKey to_quadrant(const int &p_quadrant_size) const {
- // rounding down, instead of simply rounding towards zero (truncating)
- return PosKey(
- x > 0 ? x / p_quadrant_size : (x - (p_quadrant_size - 1)) / p_quadrant_size,
- y > 0 ? y / p_quadrant_size : (y - (p_quadrant_size - 1)) / p_quadrant_size);
- }
+ // Dirty list element
+ SelfList<TileMapQuadrant> dirty_list_element;
+
+ // Quadrant coords.
+ Vector2i coords;
+
+ // TileMapCells
+ Set<Vector2i> cells;
+ // We need those two maps to sort by world position for rendering
+ // This is kind of workaround, it would be better to sort the cells directly in the "cells" set instead.
+ Map<Vector2i, Vector2i> map_to_world;
+ Map<Vector2i, Vector2i, CoordsWorldComparator> world_to_map;
+
+ // Debug.
+ RID debug_canvas_item;
+
+ // Rendering
+ List<RID> canvas_items;
+ List<RID> occluders;
+
+ // Physics.
+ List<RID> bodies;
+
+ // Navigation
+ Map<Vector2i, Vector<RID>> navigation_regions;
+
+ void operator=(const TileMapQuadrant &q) {
+ coords = q.coords;
+ debug_canvas_item = q.debug_canvas_item;
+ canvas_items = q.canvas_items;
+ occluders = q.occluders;
+ bodies = q.bodies;
+ navigation_regions = q.navigation_regions;
+ }
+
+ TileMapQuadrant(const TileMapQuadrant &q) :
+ dirty_list_element(this) {
+ coords = q.coords;
+ debug_canvas_item = q.debug_canvas_item;
+ canvas_items = q.canvas_items;
+ occluders = q.occluders;
+ bodies = q.bodies;
+ navigation_regions = q.navigation_regions;
+ }
+
+ TileMapQuadrant() :
+ dirty_list_element(this) {
+ }
+};
- PosKey(int16_t p_x, int16_t p_y) {
- x = p_x;
- y = p_y;
- }
- PosKey() {
- x = 0;
- y = 0;
- }
- };
+class TileMapPattern : public Object {
+ GDCLASS(TileMapPattern, Object);
- union Cell {
- struct {
- int32_t id : 24;
- bool flip_h : 1;
- bool flip_v : 1;
- bool transpose : 1;
- int16_t autotile_coord_x : 16;
- int16_t autotile_coord_y : 16;
- };
-
- uint64_t _u64t = 0;
- };
+ Vector2i size;
+ Map<Vector2i, TileMapCell> pattern;
- Map<PosKey, Cell> tile_map;
- List<PosKey> dirty_bitmask;
+protected:
+ static void _bind_methods();
- struct Quadrant {
- Vector2 pos;
- List<RID> canvas_items;
- RID body;
- uint32_t shape_owner_id = 0;
+public:
+ void set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile = 0);
+ bool has_cell(const Vector2i &p_coords) const;
+ void remove_cell(const Vector2i &p_coords, bool p_update_size = true);
+ int get_cell_source_id(const Vector2i &p_coords) const;
+ Vector2i get_cell_atlas_coords(const Vector2i &p_coords) const;
+ int get_cell_alternative_tile(const Vector2i &p_coords) const;
- SelfList<Quadrant> dirty_list;
+ TypedArray<Vector2i> get_used_cells() const;
- struct NavPoly {
- RID region;
- Transform2D xform;
- };
+ Vector2i get_size() const;
+ void set_size(const Vector2i &p_size);
+ bool is_empty() const;
- struct Occluder {
- RID id;
- Transform2D xform;
- };
+ void clear();
+};
- Map<PosKey, NavPoly> navpoly_ids;
- Map<PosKey, Occluder> occluder_instances;
+class TileMap : public Node2D {
+ GDCLASS(TileMap, Node2D);
- VSet<PosKey> cells;
+public:
+private:
+ friend class TileSetPlugin;
- void operator=(const Quadrant &q) {
- pos = q.pos;
- canvas_items = q.canvas_items;
- body = q.body;
- shape_owner_id = q.shape_owner_id;
- cells = q.cells;
- navpoly_ids = q.navpoly_ids;
- occluder_instances = q.occluder_instances;
- }
- Quadrant(const Quadrant &q) :
- dirty_list(this) {
- pos = q.pos;
- canvas_items = q.canvas_items;
- body = q.body;
- shape_owner_id = q.shape_owner_id;
- cells = q.cells;
- occluder_instances = q.occluder_instances;
- navpoly_ids = q.navpoly_ids;
- }
- Quadrant() :
- dirty_list(this) {}
+ enum DataFormat {
+ FORMAT_1 = 0,
+ FORMAT_2,
+ FORMAT_3
};
- Map<PosKey, Quadrant> quadrant_map;
+ Ref<TileSet> tile_set;
+ int quadrant_size;
+ Transform2D custom_transform;
+
+ // Map of cells
+ Map<Vector2i, TileMapCell> tile_map;
- SelfList<Quadrant>::List dirty_quadrant_list;
+ Vector2i _coords_to_quadrant_coords(const Vector2i &p_coords) const;
+
+ Map<Vector2i, TileMapQuadrant> quadrant_map;
+
+ SelfList<TileMapQuadrant>::List dirty_quadrant_list;
bool pending_update = false;
Rect2 rect_cache;
bool rect_cache_dirty = true;
Rect2 used_size_cache;
- bool used_size_cache_dirty = true;
- bool quadrant_order_dirty = false;
- bool use_y_sort = false;
- bool compatibility_mode = false;
- bool centered_textures = false;
- bool clip_uv = false;
- float fp_adjust = 0.00001;
- float friction = 1.0;
- float bounce = 0.0;
- uint32_t collision_layer = 1;
- uint32_t collision_mask = 1;
- mutable DataFormat format = FORMAT_1; // Assume lowest possible format if none is present
-
- TileOrigin tile_origin = TILE_ORIGIN_TOP_LEFT;
-
- int occluder_light_mask = 1;
-
- void _fix_cell_transform(Transform2D &xform, const Cell &p_cell, const Vector2 &p_offset, const Size2 &p_sc);
-
- void _add_shape(int &shape_idx, const Quadrant &p_q, const Ref<Shape2D> &p_shape, const TileSet::ShapeData &p_shape_data, const Transform2D &p_xform, const Vector2 &p_metadata);
-
- Map<PosKey, Quadrant>::Element *_create_quadrant(const PosKey &p_qk);
- void _erase_quadrant(Map<PosKey, Quadrant>::Element *Q);
- void _make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q, bool update = true);
+ bool used_size_cache_dirty;
+ mutable DataFormat format;
+
+ void _fix_cell_transform(Transform2D &xform, const TileMapCell &p_cell, const Vector2 &p_offset, const Size2 &p_sc);
+
+ Map<Vector2i, TileMapQuadrant>::Element *_create_quadrant(const Vector2i &p_qk);
+ void _erase_quadrant(Map<Vector2i, TileMapQuadrant>::Element *Q);
+ void _make_all_quadrants_dirty(bool p_update = true);
+ void _make_quadrant_dirty(Map<Vector2i, TileMapQuadrant>::Element *Q, bool p_update = true);
void _recreate_quadrants();
void _clear_quadrants();
- void _update_quadrant_space(const RID &p_space);
- void _update_quadrant_transform();
void _recompute_rect_cache();
void _update_all_items_material_state();
- _FORCE_INLINE_ void _update_item_material_state(const RID &p_canvas_item);
-
- _FORCE_INLINE_ int _get_quadrant_size() const;
void _set_tile_data(const Vector<int> &p_data);
Vector<int> _get_tile_data() const;
- void _set_old_cell_size(int p_size) { set_cell_size(Size2(p_size, p_size)); }
- int _get_old_cell_size() const { return cell_size.x; }
-
- _FORCE_INLINE_ Vector2 _map_to_world(int p_x, int p_y, bool p_ignore_ofs = false) const;
+ void _tile_set_changed();
protected:
bool _set(const StringName &p_name, const Variant &p_value);
@@ -230,9 +231,9 @@ protected:
void _notification(int p_what);
static void _bind_methods();
- virtual void _validate_property(PropertyInfo &property) const override;
-
public:
+ static Vector2i transform_coords_layout(Vector2i p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout);
+
enum {
INVALID_CELL = -1
};
@@ -244,117 +245,49 @@ public:
void set_tileset(const Ref<TileSet> &p_tileset);
Ref<TileSet> get_tileset() const;
- void set_cell_size(Size2 p_size);
- Size2 get_cell_size() const;
-
void set_quadrant_size(int p_size);
int get_quadrant_size() const;
- void set_cell(int p_x, int p_y, int p_tile, bool p_flip_x = false, bool p_flip_y = false, bool p_transpose = false, Vector2 p_autotile_coord = Vector2());
- int get_cell(int p_x, int p_y) const;
- bool is_cell_x_flipped(int p_x, int p_y) const;
- bool is_cell_y_flipped(int p_x, int p_y) const;
- bool is_cell_transposed(int p_x, int p_y) const;
- void set_cell_autotile_coord(int p_x, int p_y, const Vector2 &p_coord);
- Vector2 get_cell_autotile_coord(int p_x, int p_y) 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);
+ 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;
- void _set_celld(const Vector2 &p_pos, const Dictionary &p_data);
- void set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x = false, bool p_flip_y = false, bool p_transpose = false);
- int get_cellv(const Vector2 &p_pos) const;
+ TileMapPattern *get_pattern(TypedArray<Vector2i> p_coords_array);
+ Vector2i map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, const TileMapPattern *p_pattern);
+ void set_pattern(Vector2i p_position, const TileMapPattern *p_pattern);
- void make_bitmask_area_dirty(const Vector2 &p_pos);
- void update_bitmask_area(const Vector2 &p_pos);
- void update_bitmask_region(const Vector2 &p_start = Vector2(), const Vector2 &p_end = Vector2());
- void update_cell_bitmask(int p_x, int p_y);
- void update_dirty_bitmask();
+ // Not exposed to users
+ TileMapCell get_cell(const Vector2i &p_coords) const;
+ Map<Vector2i, TileMapQuadrant> &get_quadrant_map();
+ int get_effective_quadrant_size() const;
void update_dirty_quadrants();
- void set_collision_layer(uint32_t p_layer);
- uint32_t get_collision_layer() const;
-
- void set_collision_mask(uint32_t p_mask);
- uint32_t get_collision_mask() const;
-
- void set_collision_layer_bit(int p_bit, bool p_value);
- bool get_collision_layer_bit(int p_bit) const;
-
- void set_collision_mask_bit(int p_bit, bool p_value);
- bool get_collision_mask_bit(int p_bit) const;
-
- void set_collision_use_kinematic(bool p_use_kinematic);
- bool get_collision_use_kinematic() const;
-
- void set_collision_use_parent(bool p_use_parent);
- bool get_collision_use_parent() const;
-
- void set_collision_friction(float p_friction);
- float get_collision_friction() const;
-
- void set_collision_bounce(float p_bounce);
- float get_collision_bounce() const;
-
- void set_bake_navigation(bool p_bake_navigation);
- bool is_baking_navigation();
-
- void set_mode(Mode p_mode);
- Mode get_mode() const;
+ Vector2 map_to_world(const Vector2i &p_pos) const;
+ Vector2i world_to_map(const Vector2 &p_pos) const;
- void set_half_offset(HalfOffset p_half_offset);
- HalfOffset get_half_offset() const;
-
- void set_tile_origin(TileOrigin p_tile_origin);
- TileOrigin get_tile_origin() const;
-
- void set_custom_transform(const Transform2D &p_xform);
- Transform2D get_custom_transform() const;
-
- Transform2D get_cell_transform() const;
- Vector2 get_cell_draw_offset() const;
-
- Vector2 map_to_world(const Vector2 &p_pos, bool p_ignore_ofs = false) const;
- Vector2 world_to_map(const Vector2 &p_pos) const;
-
- void set_y_sort_enabled(bool p_enable);
- bool is_y_sort_enabled() const;
-
- void set_compatibility_mode(bool p_enable);
- bool is_compatibility_mode_enabled() const;
-
- void set_centered_textures(bool p_enable);
- bool is_centered_textures_enabled() const;
+ bool is_existing_neighbor(TileSet::CellNeighbor p_cell_neighbor) const;
+ Vector2i get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const;
TypedArray<Vector2i> get_used_cells() const;
- TypedArray<Vector2i> get_used_cells_by_index(int p_index) const;
Rect2 get_used_rect(); // Not const because of cache
- void set_occluder_light_mask(int p_mask);
- int get_occluder_light_mask() const;
-
+ // Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems
virtual void set_light_mask(int p_light_mask) override;
-
virtual void set_material(const Ref<Material> &p_material) override;
-
virtual void set_use_parent_material(bool p_use_parent_material) override;
-
- void set_clip_uv(bool p_enable);
- bool get_clip_uv() const;
-
- TypedArray<String> get_configuration_warnings() const override;
-
virtual void set_texture_filter(CanvasItem::TextureFilter p_texture_filter) override;
-
virtual void set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) override;
void fix_invalid_tiles();
void clear();
+ // Helpers
+ TypedArray<Vector2i> get_surrounding_tiles(Vector2i coords);
+ void draw_cells_outline(Control *p_control, Set<Vector2i> p_cells, Color p_color, Transform2D p_transform = Transform2D());
+
TileMap();
~TileMap();
};
-
-VARIANT_ENUM_CAST(TileMap::Mode);
-VARIANT_ENUM_CAST(TileMap::HalfOffset);
-VARIANT_ENUM_CAST(TileMap::TileOrigin);
-
#endif // TILE_MAP_H
diff --git a/scene/3d/collision_object_3d.cpp b/scene/3d/collision_object_3d.cpp
index 688509a979..914b3ad816 100644
--- a/scene/3d/collision_object_3d.cpp
+++ b/scene/3d/collision_object_3d.cpp
@@ -31,12 +31,27 @@
#include "collision_object_3d.h"
#include "core/config/engine.h"
-#include "mesh_instance_3d.h"
#include "scene/scene_string_names.h"
#include "servers/physics_server_3d.h"
void CollisionObject3D::_notification(int p_what) {
switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ if (_are_collision_shapes_visible()) {
+ debug_shape_old_transform = get_global_transform();
+ for (Map<uint32_t, ShapeData>::Element *E = shapes.front(); E; E = E->next()) {
+ debug_shapes_to_update.insert(E->key());
+ }
+ _update_debug_shapes();
+ }
+ } break;
+
+ case NOTIFICATION_EXIT_TREE: {
+ if (debug_shapes_count > 0) {
+ _clear_debug_shapes();
+ }
+ } break;
+
case NOTIFICATION_ENTER_WORLD: {
if (area) {
PhysicsServer3D::get_singleton()->area_set_transform(rid, get_global_transform());
@@ -62,6 +77,8 @@ void CollisionObject3D::_notification(int p_what) {
PhysicsServer3D::get_singleton()->body_set_state(rid, PhysicsServer3D::BODY_STATE_TRANSFORM, get_global_transform());
}
+ _on_transform_changed();
+
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
_update_pickable();
@@ -75,11 +92,6 @@ void CollisionObject3D::_notification(int p_what) {
}
} break;
- case NOTIFICATION_PREDELETE: {
- if (debug_shape_count > 0) {
- _clear_debug_shapes();
- }
- } break;
}
}
@@ -175,6 +187,33 @@ void CollisionObject3D::_update_pickable() {
}
}
+bool CollisionObject3D::_are_collision_shapes_visible() {
+ return is_inside_tree() && get_tree()->is_debugging_collisions_hint() && !Engine::get_singleton()->is_editor_hint();
+}
+
+void CollisionObject3D::_update_shape_data(uint32_t p_owner) {
+ if (_are_collision_shapes_visible()) {
+ if (debug_shapes_to_update.is_empty()) {
+ callable_mp(this, &CollisionObject3D::_update_debug_shapes).call_deferred({}, 0);
+ }
+ debug_shapes_to_update.insert(p_owner);
+ }
+}
+
+void CollisionObject3D::_shape_changed(const Ref<Shape3D> &p_shape) {
+ for (Map<uint32_t, ShapeData>::Element *E = shapes.front(); E; E = E->next()) {
+ ShapeData &shapedata = E->get();
+ ShapeData::ShapeBase *shapes = shapedata.shapes.ptrw();
+ for (int i = 0; i < shapedata.shapes.size(); i++) {
+ ShapeData::ShapeBase &s = shapes[i];
+ if (s.shape == p_shape && s.debug_shape.is_valid()) {
+ Ref<Mesh> mesh = s.shape->get_debug_mesh();
+ RS::get_singleton()->instance_set_base(s.debug_shape, mesh->get_rid());
+ }
+ }
+ }
+}
+
void CollisionObject3D::_update_debug_shapes() {
for (Set<uint32_t>::Element *shapedata_idx = debug_shapes_to_update.front(); shapedata_idx; shapedata_idx = shapedata_idx->next()) {
if (shapes.has(shapedata_idx->get())) {
@@ -182,23 +221,30 @@ void CollisionObject3D::_update_debug_shapes() {
ShapeData::ShapeBase *shapes = shapedata.shapes.ptrw();
for (int i = 0; i < shapedata.shapes.size(); i++) {
ShapeData::ShapeBase &s = shapes[i];
- if (s.debug_shape) {
- s.debug_shape->queue_delete();
- s.debug_shape = nullptr;
- --debug_shape_count;
- }
if (s.shape.is_null() || shapedata.disabled) {
+ if (s.debug_shape.is_valid()) {
+ RS::get_singleton()->free(s.debug_shape);
+ s.debug_shape = RID();
+ --debug_shapes_count;
+ }
continue;
}
+ if (s.debug_shape.is_null()) {
+ s.debug_shape = RS::get_singleton()->instance_create();
+ RS::get_singleton()->instance_set_scenario(s.debug_shape, get_world_3d()->get_scenario());
+
+ if (!s.shape->is_connected("changed", callable_mp(this, &CollisionObject3D::_shape_changed))) {
+ s.shape->connect("changed", callable_mp(this, &CollisionObject3D::_shape_changed),
+ varray(s.shape), CONNECT_DEFERRED);
+ }
+
+ ++debug_shapes_count;
+ }
+
Ref<Mesh> mesh = s.shape->get_debug_mesh();
- MeshInstance3D *mi = memnew(MeshInstance3D);
- mi->set_transform(shapedata.xform);
- mi->set_mesh(mesh);
- add_child(mi);
- mi->force_update_transform();
- s.debug_shape = mi;
- ++debug_shape_count;
+ RS::get_singleton()->instance_set_base(s.debug_shape, mesh->get_rid());
+ RS::get_singleton()->instance_set_transform(s.debug_shape, get_global_transform() * shapedata.xform);
}
}
}
@@ -211,23 +257,28 @@ void CollisionObject3D::_clear_debug_shapes() {
ShapeData::ShapeBase *shapes = shapedata.shapes.ptrw();
for (int i = 0; i < shapedata.shapes.size(); i++) {
ShapeData::ShapeBase &s = shapes[i];
- if (s.debug_shape) {
- s.debug_shape->queue_delete();
- s.debug_shape = nullptr;
- --debug_shape_count;
+ if (s.debug_shape.is_valid()) {
+ RS::get_singleton()->free(s.debug_shape);
+ s.debug_shape = RID();
+ if (s.shape.is_valid() && s.shape->is_connected("changed", callable_mp(this, &CollisionObject3D::_update_shape_data))) {
+ s.shape->disconnect("changed", callable_mp(this, &CollisionObject3D::_update_shape_data));
+ }
}
}
}
-
- debug_shape_count = 0;
+ debug_shapes_count = 0;
}
-void CollisionObject3D::_update_shape_data(uint32_t p_owner) {
- if (is_inside_tree() && get_tree()->is_debugging_collisions_hint() && !Engine::get_singleton()->is_editor_hint()) {
- if (debug_shapes_to_update.is_empty()) {
- call_deferred("_update_debug_shapes");
+void CollisionObject3D::_on_transform_changed() {
+ if (debug_shapes_count > 0 && !debug_shape_old_transform.is_equal_approx(get_global_transform())) {
+ debug_shape_old_transform = get_global_transform();
+ for (Map<uint32_t, ShapeData>::Element *E = shapes.front(); E; E = E->next()) {
+ ShapeData &shapedata = E->get();
+ const ShapeData::ShapeBase *shapes = shapedata.shapes.ptr();
+ for (int i = 0; i < shapedata.shapes.size(); i++) {
+ RS::get_singleton()->instance_set_transform(shapes[i].debug_shape, debug_shape_old_transform * shapedata.xform);
+ }
}
- debug_shapes_to_update.insert(p_owner);
}
}
@@ -270,8 +321,6 @@ void CollisionObject3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("shape_owner_clear_shapes", "owner_id"), &CollisionObject3D::shape_owner_clear_shapes);
ClassDB::bind_method(D_METHOD("shape_find_owner", "shape_index"), &CollisionObject3D::shape_find_owner);
- ClassDB::bind_method(D_METHOD("_update_debug_shapes"), &CollisionObject3D::_update_debug_shapes);
-
BIND_VMETHOD(MethodInfo("_input_event", PropertyInfo(Variant::OBJECT, "camera"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"), PropertyInfo(Variant::VECTOR3, "click_position"), PropertyInfo(Variant::VECTOR3, "click_normal"), PropertyInfo(Variant::INT, "shape_idx")));
ADD_SIGNAL(MethodInfo("input_event", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"), PropertyInfo(Variant::VECTOR3, "click_position"), PropertyInfo(Variant::VECTOR3, "click_normal"), PropertyInfo(Variant::INT, "shape_idx")));
@@ -316,7 +365,11 @@ void CollisionObject3D::shape_owner_set_disabled(uint32_t p_owner, bool p_disabl
ERR_FAIL_COND(!shapes.has(p_owner));
ShapeData &sd = shapes[p_owner];
+ if (sd.disabled == p_disabled) {
+ return;
+ }
sd.disabled = p_disabled;
+
for (int i = 0; i < sd.shapes.size(); i++) {
if (area) {
PhysicsServer3D::get_singleton()->area_set_shape_disabled(rid, sd.shapes[i].index, p_disabled);
@@ -421,7 +474,7 @@ void CollisionObject3D::shape_owner_remove_shape(uint32_t p_owner, int p_shape)
ERR_FAIL_COND(!shapes.has(p_owner));
ERR_FAIL_INDEX(p_shape, shapes[p_owner].shapes.size());
- const ShapeData::ShapeBase &s = shapes[p_owner].shapes[p_shape];
+ ShapeData::ShapeBase &s = shapes[p_owner].shapes.write[p_shape];
int index_to_remove = s.index;
if (area) {
@@ -430,8 +483,12 @@ void CollisionObject3D::shape_owner_remove_shape(uint32_t p_owner, int p_shape)
PhysicsServer3D::get_singleton()->body_remove_shape(rid, index_to_remove);
}
- if (s.debug_shape) {
- s.debug_shape->queue_delete();
+ if (s.debug_shape.is_valid()) {
+ RS::get_singleton()->free(s.debug_shape);
+ if (s.shape.is_valid() && s.shape->is_connected("changed", callable_mp(this, &CollisionObject3D::_shape_changed))) {
+ s.shape->disconnect("changed", callable_mp(this, &CollisionObject3D::_shape_changed));
+ }
+ --debug_shapes_count;
}
shapes[p_owner].shapes.remove(p_shape);
diff --git a/scene/3d/collision_object_3d.h b/scene/3d/collision_object_3d.h
index e3901979d3..7ff3c5efde 100644
--- a/scene/3d/collision_object_3d.h
+++ b/scene/3d/collision_object_3d.h
@@ -48,7 +48,7 @@ class CollisionObject3D : public Node3D {
Object *owner = nullptr;
Transform xform;
struct ShapeBase {
- Node *debug_shape = nullptr;
+ RID debug_shape;
Ref<Shape3D> shape;
int index = 0;
};
@@ -65,25 +65,30 @@ class CollisionObject3D : public Node3D {
bool ray_pickable = true;
Set<uint32_t> debug_shapes_to_update;
- int debug_shape_count = 0;
+ int debug_shapes_count = 0;
+ Transform debug_shape_old_transform;
void _update_pickable();
+ bool _are_collision_shapes_visible();
void _update_shape_data(uint32_t p_owner);
+ void _shape_changed(const Ref<Shape3D> &p_shape);
+ void _update_debug_shapes();
+ void _clear_debug_shapes();
protected:
CollisionObject3D(RID p_rid, bool p_area);
void _notification(int p_what);
static void _bind_methods();
+
+ void _on_transform_changed();
+
friend class Viewport;
virtual void _input_event(Node *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape);
virtual void _mouse_enter();
virtual void _mouse_exit();
- void _update_debug_shapes();
- void _clear_debug_shapes();
-
public:
void set_collision_layer(uint32_t p_layer);
uint32_t get_collision_layer() const;
diff --git a/scene/3d/collision_shape_3d.cpp b/scene/3d/collision_shape_3d.cpp
index bec87914c0..70d9cebb83 100644
--- a/scene/3d/collision_shape_3d.cpp
+++ b/scene/3d/collision_shape_3d.cpp
@@ -161,12 +161,10 @@ void CollisionShape3D::set_shape(const Ref<Shape3D> &p_shape) {
}
if (!shape.is_null()) {
shape->unregister_owner(this);
- shape->disconnect("changed", callable_mp(this, &CollisionShape3D::_shape_changed));
}
shape = p_shape;
if (!shape.is_null()) {
shape->register_owner(this);
- shape->connect("changed", callable_mp(this, &CollisionShape3D::_shape_changed));
}
update_gizmo();
if (parent) {
@@ -176,8 +174,9 @@ void CollisionShape3D::set_shape(const Ref<Shape3D> &p_shape) {
}
}
- if (is_inside_tree()) {
- _shape_changed();
+ if (is_inside_tree() && parent) {
+ // If this is a heightfield shape our center may have changed
+ _update_in_shape_owner(true);
}
update_configuration_warnings();
}
@@ -209,10 +208,3 @@ CollisionShape3D::~CollisionShape3D() {
}
//RenderingServer::get_singleton()->free(indicator);
}
-
-void CollisionShape3D::_shape_changed() {
- // If this is a heightfield shape our center may have changed
- if (parent) {
- _update_in_shape_owner(true);
- }
-}
diff --git a/scene/3d/collision_shape_3d.h b/scene/3d/collision_shape_3d.h
index 56a4ae3039..f69c1e38eb 100644
--- a/scene/3d/collision_shape_3d.h
+++ b/scene/3d/collision_shape_3d.h
@@ -47,8 +47,6 @@ class CollisionShape3D : public Node3D {
bool disabled = false;
protected:
- void _shape_changed();
-
void _update_in_shape_owner(bool p_xform_only = false);
protected:
diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp
index 5339b8a8da..50044ddc67 100644
--- a/scene/3d/gpu_particles_3d.cpp
+++ b/scene/3d/gpu_particles_3d.cpp
@@ -613,6 +613,7 @@ void GPUParticles3D::_bind_methods() {
GPUParticles3D::GPUParticles3D() {
particles = RS::get_singleton()->particles_create();
+ RS::get_singleton()->particles_set_mode(particles, RS::PARTICLES_MODE_3D);
set_base(particles);
one_shot = false; // Needed so that set_emitting doesn't access uninitialized values
set_emitting(true);
diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp
index 93d3e946fd..dd1a797568 100644
--- a/scene/3d/physics_body_3d.cpp
+++ b/scene/3d/physics_body_3d.cpp
@@ -292,6 +292,7 @@ void RigidBody3D::_direct_state_changed(Object *p_state) {
get_script_instance()->call("_integrate_forces", state);
}
set_ignore_transform_notification(false);
+ _on_transform_changed();
if (contact_monitor) {
contact_monitor->locked = true;
@@ -1985,6 +1986,7 @@ void PhysicalBone3D::_direct_state_changed(Object *p_state) {
set_ignore_transform_notification(true);
set_global_transform(global_transform);
set_ignore_transform_notification(false);
+ _on_transform_changed();
// Update skeleton
if (parent_skeleton) {
diff --git a/scene/3d/ray_cast_3d.cpp b/scene/3d/ray_cast_3d.cpp
index 95638ce514..475f8c07fd 100644
--- a/scene/3d/ray_cast_3d.cpp
+++ b/scene/3d/ray_cast_3d.cpp
@@ -428,7 +428,7 @@ void RayCast3D::_update_debug_shape_material(bool p_check_collision) {
color = get_tree()->get_debug_collisions_color();
}
- if (p_check_collision) {
+ if (p_check_collision && collided) {
if ((color.get_h() < 0.055 || color.get_h() > 0.945) && color.get_s() > 0.5 && color.get_v() > 0.5) {
// If base color is already quite reddish, highlight collision with green color
color = Color(0.0, 1.0, 0.0, color.a);
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index ebbb8985c9..59233708f6 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -237,53 +237,57 @@ void Skeleton3D::_notification(int p_what) {
for (int i = 0; i < len; i++) {
Bone &b = bonesptr[order[i]];
- if (b.global_pose_override_amount >= 0.999) {
- b.pose_global = b.global_pose_override;
- } else {
- if (b.disable_rest) {
- if (b.enabled) {
- Transform pose = b.pose;
- if (b.custom_pose_enable) {
- pose = b.custom_pose * pose;
- }
- if (b.parent >= 0) {
- b.pose_global = bonesptr[b.parent].pose_global * pose;
- } else {
- b.pose_global = pose;
- }
+ if (b.disable_rest) {
+ if (b.enabled) {
+ Transform pose = b.pose;
+ if (b.custom_pose_enable) {
+ pose = b.custom_pose * pose;
+ }
+ if (b.parent >= 0) {
+ b.pose_global = bonesptr[b.parent].pose_global * pose;
+ b.pose_global_no_override = bonesptr[b.parent].pose_global * pose;
} else {
- if (b.parent >= 0) {
- b.pose_global = bonesptr[b.parent].pose_global;
- } else {
- b.pose_global = Transform();
- }
+ b.pose_global = pose;
+ b.pose_global_no_override = pose;
}
-
} else {
- if (b.enabled) {
- Transform pose = b.pose;
- if (b.custom_pose_enable) {
- pose = b.custom_pose * pose;
- }
- if (b.parent >= 0) {
- b.pose_global = bonesptr[b.parent].pose_global * (b.rest * pose);
- } else {
- b.pose_global = b.rest * pose;
- }
+ if (b.parent >= 0) {
+ b.pose_global = bonesptr[b.parent].pose_global;
+ b.pose_global_no_override = bonesptr[b.parent].pose_global;
} else {
- if (b.parent >= 0) {
- b.pose_global = bonesptr[b.parent].pose_global * b.rest;
- } else {
- b.pose_global = b.rest;
- }
+ b.pose_global = Transform();
+ b.pose_global_no_override = Transform();
}
}
- if (b.global_pose_override_amount >= CMP_EPSILON) {
- b.pose_global = b.pose_global.interpolate_with(b.global_pose_override, b.global_pose_override_amount);
+ } else {
+ if (b.enabled) {
+ Transform pose = b.pose;
+ if (b.custom_pose_enable) {
+ pose = b.custom_pose * pose;
+ }
+ if (b.parent >= 0) {
+ b.pose_global = bonesptr[b.parent].pose_global * (b.rest * pose);
+ b.pose_global_no_override = bonesptr[b.parent].pose_global * (b.rest * pose);
+ } else {
+ b.pose_global = b.rest * pose;
+ b.pose_global_no_override = b.rest * pose;
+ }
+ } else {
+ if (b.parent >= 0) {
+ b.pose_global = bonesptr[b.parent].pose_global * b.rest;
+ b.pose_global_no_override = bonesptr[b.parent].pose_global * b.rest;
+ } else {
+ b.pose_global = b.rest;
+ b.pose_global_no_override = b.rest;
+ }
}
}
+ if (b.global_pose_override_amount >= CMP_EPSILON) {
+ b.pose_global = b.pose_global.interpolate_with(b.global_pose_override, b.global_pose_override_amount);
+ }
+
if (b.global_pose_override_reset) {
b.global_pose_override_amount = 0.0;
}
@@ -408,6 +412,14 @@ Transform Skeleton3D::get_bone_global_pose(int p_bone) const {
return bones[p_bone].pose_global;
}
+Transform Skeleton3D::get_bone_global_pose_no_override(int p_bone) const {
+ ERR_FAIL_INDEX_V(p_bone, bones.size(), Transform());
+ if (dirty) {
+ const_cast<Skeleton3D *>(this)->notification(NOTIFICATION_UPDATE_SKELETON);
+ }
+ return bones[p_bone].pose_global_no_override;
+}
+
// skeleton creation api
void Skeleton3D::add_bone(const String &p_name) {
ERR_FAIL_COND(p_name == "" || p_name.find(":") != -1 || p_name.find("/") != -1);
@@ -912,6 +924,7 @@ void Skeleton3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear_bones_global_pose_override"), &Skeleton3D::clear_bones_global_pose_override);
ClassDB::bind_method(D_METHOD("set_bone_global_pose_override", "bone_idx", "pose", "amount", "persistent"), &Skeleton3D::set_bone_global_pose_override, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_bone_global_pose", "bone_idx"), &Skeleton3D::get_bone_global_pose);
+ ClassDB::bind_method(D_METHOD("get_bone_global_pose_no_override", "bone_idx"), &Skeleton3D::get_bone_global_pose_no_override);
ClassDB::bind_method(D_METHOD("get_bone_custom_pose", "bone_idx"), &Skeleton3D::get_bone_custom_pose);
ClassDB::bind_method(D_METHOD("set_bone_custom_pose", "bone_idx", "custom_pose"), &Skeleton3D::set_bone_custom_pose);
diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h
index 2941ac2c45..508cd7c329 100644
--- a/scene/3d/skeleton_3d.h
+++ b/scene/3d/skeleton_3d.h
@@ -83,6 +83,7 @@ private:
Transform pose;
Transform pose_global;
+ Transform pose_global_no_override;
bool custom_pose_enable = false;
Transform custom_pose;
@@ -160,6 +161,7 @@ public:
void set_bone_rest(int p_bone, const Transform &p_rest);
Transform get_bone_rest(int p_bone) const;
Transform get_bone_global_pose(int p_bone) const;
+ Transform get_bone_global_pose_no_override(int p_bone) const;
void clear_bones_global_pose_override();
void set_bone_global_pose_override(int p_bone, const Transform &p_pose, float p_amount, bool p_persistent = false);
diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp
index 898f94ccc1..bd1c202205 100644
--- a/scene/3d/skeleton_ik_3d.cpp
+++ b/scene/3d/skeleton_ik_3d.cpp
@@ -246,7 +246,7 @@ void FabrikInverseKinematic::make_goal(Task *p_task, const Transform &p_inverse_
p_task->end_effectors.write[0].goal_transform = p_inverse_transf * p_task->goal_global_transform;
} else {
// End effector in local transform
- const Transform end_effector_pose(p_task->skeleton->get_bone_global_pose(p_task->end_effectors[0].tip_bone));
+ const Transform end_effector_pose(p_task->skeleton->get_bone_global_pose_no_override(p_task->end_effectors[0].tip_bone));
// Update the end_effector (local transform) by blending with current pose
p_task->end_effectors.write[0].goal_transform = end_effector_pose.interpolate_with(p_inverse_transf * p_task->goal_global_transform, blending_delta);
@@ -270,18 +270,7 @@ void FabrikInverseKinematic::solve(Task *p_task, real_t blending_delta, bool ove
return; // Skip solving
}
- p_task->skeleton->set_bone_global_pose_override(p_task->chain.chain_root.bone, Transform(), 0.0, true);
-
- if (p_task->chain.middle_chain_item) {
- p_task->skeleton->set_bone_global_pose_override(p_task->chain.middle_chain_item->bone, Transform(), 0.0, true);
- }
-
- for (int i = 0; i < p_task->chain.tips.size(); i += 1) {
- p_task->skeleton->set_bone_global_pose_override(p_task->chain.tips[i].chain_item->bone, Transform(), 0.0, true);
- }
-
- // Update the transforms to their global poses
- // (Needed to sync IK with animation)
+ // Update the initial root transform so its synced with any animation changes
_update_chain(p_task->skeleton, &p_task->chain.chain_root);
make_goal(p_task, p_task->skeleton->get_global_transform().affine_inverse(), blending_delta);
@@ -298,48 +287,22 @@ void FabrikInverseKinematic::solve(Task *p_task, real_t blending_delta, bool ove
Transform new_bone_pose(ci->initial_transform);
new_bone_pose.origin = ci->current_pos;
- // The root bone needs to be rotated differently so it isn't frozen in place.
- if (ci == &p_task->chain.chain_root && !ci->children.is_empty()) {
- new_bone_pose = new_bone_pose.looking_at(ci->children[0].current_pos);
- const Vector3 bone_rest_dir = p_task->skeleton->get_bone_rest(ci->children[0].bone).origin.normalized().abs();
- const Vector3 bone_rest_dir_abs = bone_rest_dir.abs();
- if (bone_rest_dir_abs.x > bone_rest_dir_abs.y && bone_rest_dir_abs.x > bone_rest_dir_abs.z) {
- if (bone_rest_dir.x < 0) {
- new_bone_pose.basis.rotate_local(Vector3(0, 1, 0), -Math_PI / 2.0f);
- } else {
- new_bone_pose.basis.rotate_local(Vector3(0, 1, 0), Math_PI / 2.0f);
- }
- } else if (bone_rest_dir_abs.y > bone_rest_dir_abs.x && bone_rest_dir_abs.y > bone_rest_dir_abs.z) {
- if (bone_rest_dir.y < 0) {
- new_bone_pose.basis.rotate_local(Vector3(1, 0, 0), Math_PI / 2.0f);
- } else {
- new_bone_pose.basis.rotate_local(Vector3(1, 0, 0), -Math_PI / 2.0f);
- }
- } else {
- if (bone_rest_dir.z < 0) {
- // Do nothing!
- } else {
- new_bone_pose.basis.rotate_local(Vector3(0, 0, 1), Math_PI);
- }
- }
- } else {
- if (!ci->children.is_empty()) {
- /// Rotate basis
- const Vector3 initial_ori((ci->children[0].initial_transform.origin - ci->initial_transform.origin).normalized());
- const Vector3 rot_axis(initial_ori.cross(ci->current_ori).normalized());
+ if (!ci->children.is_empty()) {
+ /// Rotate basis
+ const Vector3 initial_ori((ci->children[0].initial_transform.origin - ci->initial_transform.origin).normalized());
+ const Vector3 rot_axis(initial_ori.cross(ci->current_ori).normalized());
- if (rot_axis[0] != 0 && rot_axis[1] != 0 && rot_axis[2] != 0) {
- const real_t rot_angle(Math::acos(CLAMP(initial_ori.dot(ci->current_ori), -1, 1)));
- new_bone_pose.basis.rotate(rot_axis, rot_angle);
- }
+ if (rot_axis[0] != 0 && rot_axis[1] != 0 && rot_axis[2] != 0) {
+ const real_t rot_angle(Math::acos(CLAMP(initial_ori.dot(ci->current_ori), -1, 1)));
+ new_bone_pose.basis.rotate(rot_axis, rot_angle);
+ }
+ } else {
+ // Set target orientation to tip
+ if (override_tip_basis) {
+ new_bone_pose.basis = p_task->chain.tips[0].end_effector->goal_transform.basis;
} else {
- // Set target orientation to tip
- if (override_tip_basis) {
- new_bone_pose.basis = p_task->chain.tips[0].end_effector->goal_transform.basis;
- } else {
- new_bone_pose.basis = new_bone_pose.basis * p_task->chain.tips[0].end_effector->goal_transform.basis;
- }
+ new_bone_pose.basis = new_bone_pose.basis * p_task->chain.tips[0].end_effector->goal_transform.basis;
}
}
@@ -362,7 +325,7 @@ void FabrikInverseKinematic::_update_chain(const Skeleton3D *p_sk, ChainItem *p_
return;
}
- p_chain_item->initial_transform = p_sk->get_bone_global_pose(p_chain_item->bone);
+ p_chain_item->initial_transform = p_sk->get_bone_global_pose_no_override(p_chain_item->bone);
p_chain_item->current_pos = p_chain_item->initial_transform.origin;
ChainItem *items = p_chain_item->children.ptrw();
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
index 44f2d38a84..2ad871ba61 100644
--- a/scene/animation/animation_tree.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -597,9 +597,9 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
if (path.get_subname_count() == 1 && Object::cast_to<Skeleton3D>(spatial)) {
Skeleton3D *sk = Object::cast_to<Skeleton3D>(spatial);
+ track_xform->skeleton = sk;
int bone_idx = sk->find_bone(path.get_subname(0));
if (bone_idx != -1) {
- track_xform->skeleton = sk;
track_xform->bone_idx = bone_idx;
}
}
@@ -1205,7 +1205,7 @@ void AnimationTree::_process_graph(float p_delta) {
} else if (t->skeleton && t->bone_idx >= 0) {
t->skeleton->set_bone_pose(t->bone_idx, xform);
- } else {
+ } else if (!t->skeleton) {
t->spatial->set_transform(xform);
}
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 114abbd4da..fdee136b82 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -470,6 +470,19 @@ void ColorPicker::_update_text_value() {
c_text->set_visible(visible);
}
+void ColorPicker::_sample_input(const Ref<InputEvent> &p_event) {
+ const Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ const Rect2 rect_old = Rect2(Point2(), Size2(sample->get_size().width * 0.5, sample->get_size().height * 0.95));
+ if (rect_old.has_point(mb->get_position())) {
+ // Revert to the old color when left-clicking the old color sample.
+ color = old_color;
+ _update_color();
+ emit_signal("color_changed", color);
+ }
+ }
+}
+
void ColorPicker::_sample_draw() {
// Covers the right half of the sample if the old color is being displayed,
// or the whole sample if it's not being displayed.
@@ -1067,6 +1080,7 @@ ColorPicker::ColorPicker() :
hb_smpl->add_child(sample);
sample->set_h_size_flags(SIZE_EXPAND_FILL);
+ sample->connect("gui_input", callable_mp(this, &ColorPicker::_sample_input));
sample->connect("draw", callable_mp(this, &ColorPicker::_sample_draw));
btn_pick->set_flat(true);
@@ -1210,7 +1224,9 @@ ColorPicker::ColorPicker() :
void ColorPickerButton::_about_to_popup() {
set_pressed(true);
- picker->set_old_color(color);
+ if (picker) {
+ picker->set_old_color(color);
+ }
}
void ColorPickerButton::_color_changed(const Color &p_color) {
diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h
index 13fe5fd60e..400074e6e9 100644
--- a/scene/gui/color_picker.h
+++ b/scene/gui/color_picker.h
@@ -108,6 +108,7 @@ private:
void _update_presets();
void _update_text_value();
void _text_type_toggled();
+ void _sample_input(const Ref<InputEvent> &p_event);
void _sample_draw();
void _hsv_draw(int p_which, Control *c);
void _slider_draw(int p_which);
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 191f94b2b8..ce5eef93aa 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -345,72 +345,72 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const {
List<StringName> names;
theme->get_icon_list(get_class_name(), &names);
for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+ uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
if (data.icon_override.has(E->get())) {
- hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+ usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
}
- p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_icons/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", hint));
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_icons/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", usage));
}
}
{
List<StringName> names;
theme->get_stylebox_list(get_class_name(), &names);
for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+ uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
if (data.style_override.has(E->get())) {
- hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+ usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
}
- p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_styles/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "StyleBox", hint));
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_styles/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "StyleBox", usage));
}
}
{
List<StringName> names;
theme->get_font_list(get_class_name(), &names);
for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+ uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
if (data.font_override.has(E->get())) {
- hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+ usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
}
- p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_fonts/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "Font", hint));
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_fonts/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "Font", usage));
}
}
{
List<StringName> names;
theme->get_font_size_list(get_class_name(), &names);
for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+ uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
if (data.font_size_override.has(E->get())) {
- hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+ usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
}
- p_list->push_back(PropertyInfo(Variant::INT, "custom_font_sizes/" + E->get(), PROPERTY_HINT_NONE, "", hint));
+ p_list->push_back(PropertyInfo(Variant::INT, "custom_font_sizes/" + E->get(), PROPERTY_HINT_NONE, "", usage));
}
}
{
List<StringName> names;
theme->get_color_list(get_class_name(), &names);
for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+ uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
if (data.color_override.has(E->get())) {
- hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+ usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
}
- p_list->push_back(PropertyInfo(Variant::COLOR, "custom_colors/" + E->get(), PROPERTY_HINT_NONE, "", hint));
+ p_list->push_back(PropertyInfo(Variant::COLOR, "custom_colors/" + E->get(), PROPERTY_HINT_NONE, "", usage));
}
}
{
List<StringName> names;
theme->get_constant_list(get_class_name(), &names);
for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+ uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
if (data.constant_override.has(E->get())) {
- hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+ usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
}
- p_list->push_back(PropertyInfo(Variant::INT, "custom_constants/" + E->get(), PROPERTY_HINT_RANGE, "-16384,16384", hint));
+ p_list->push_back(PropertyInfo(Variant::INT, "custom_constants/" + E->get(), PROPERTY_HINT_RANGE, "-16384,16384", usage));
}
}
}
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index 4fddb4b661..0bdae2b118 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -406,6 +406,9 @@ void ItemList::remove_item(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
items.remove(p_idx);
+ if (current == p_idx) {
+ current = -1;
+ }
update();
shape_changed = true;
defer_select_single = -1;
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index eb836b3bf7..bfd739788f 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -557,7 +557,7 @@ void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
}
Control::CursorShape LineEdit::get_cursor_shape(const Point2 &p_pos) const {
- if (!text.is_empty() && is_editable() && _is_over_clear_button(p_pos)) {
+ if ((!text.is_empty() && is_editable() && _is_over_clear_button(p_pos)) || (!is_editable() && (!is_selecting_enabled() || text.is_empty()))) {
return CURSOR_ARROW;
}
return Control::get_cursor_shape(p_pos);
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index ded912591f..c924f89709 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -4450,7 +4450,7 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const {
return CURSOR_POINTING_HAND;
}
- if ((completion_active && completion_rect.has_point(p_pos))) {
+ if ((completion_active && completion_rect.has_point(p_pos)) || (is_readonly() && (!is_selecting_enabled() || text.size() == 0))) {
return CURSOR_ARROW;
}
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index d10ce584b7..7028d7aed4 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -3178,7 +3178,6 @@ void Tree::_notification(int p_what) {
RID ci = get_canvas_item();
Ref<StyleBox> bg = cache.bg;
- Ref<StyleBox> bg_focus = get_theme_stylebox("bg_focus");
Color font_outline_color = get_theme_color("font_outline_color");
int outline_size = get_theme_constant("outline_size");
@@ -3187,11 +3186,6 @@ void Tree::_notification(int p_what) {
Size2 draw_size = get_size() - bg->get_minimum_size();
bg->draw(ci, Rect2(Point2(), get_size()));
- if (has_focus()) {
- RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true);
- bg_focus->draw(ci, Rect2(Point2(), get_size()));
- RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false);
- }
int tbh = _get_title_button_height();
@@ -3225,6 +3219,15 @@ void Tree::_notification(int p_what) {
columns[i].text_buf->draw(ci, text_pos, cache.title_button_color);
}
}
+
+ // Draw the background focus outline last, so that it is drawn in front of the section headings.
+ // Otherwise, section heading backgrounds can appear to be in front of the focus outline when scrolling.
+ if (has_focus()) {
+ RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true);
+ const Ref<StyleBox> bg_focus = get_theme_stylebox("bg_focus");
+ bg_focus->draw(ci, Rect2(Point2(), get_size()));
+ RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false);
+ }
}
if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || p_what == NOTIFICATION_TRANSLATION_CHANGED) {
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index b16532676f..5b5eb946f0 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -655,6 +655,9 @@ void register_scene_types() {
ClassDB::register_class<GrooveJoint2D>();
ClassDB::register_class<DampedSpringJoint2D>();
ClassDB::register_class<TileSet>();
+ ClassDB::register_virtual_class<TileSetSource>();
+ ClassDB::register_class<TileSetAtlasSource>();
+ ClassDB::register_class<TileData>();
ClassDB::register_class<TileMap>();
ClassDB::register_class<ParallaxBackground>();
ClassDB::register_class<ParallaxLayer>();
diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp
index 84be69d0d6..c4b8a56f54 100644
--- a/scene/resources/tile_set.cpp
+++ b/scene/resources/tile_set.cpp
@@ -30,1150 +30,4221 @@
#include "tile_set.h"
-#include "core/config/engine.h"
#include "core/math/geometry_2d.h"
-#include "core/variant/array.h"
+#include "scene/2d/navigation_region_2d.h"
+#include "scene/gui/control.h"
+#include "scene/resources/convex_polygon_shape_2d.h"
+#include "servers/navigation_server_2d.h"
-bool TileSet::_set(const StringName &p_name, const Variant &p_value) {
- String n = p_name;
- int slash = n.find("/");
- if (slash == -1) {
- return false;
- }
- int id = String::to_int(n.get_data(), slash);
-
- if (!tile_map.has(id)) {
- create_tile(id);
- }
- String what = n.substr(slash + 1, n.length());
-
- if (what == "name") {
- tile_set_name(id, p_value);
- } else if (what == "texture") {
- tile_set_texture(id, p_value);
- } else if (what == "tex_offset") {
- tile_set_texture_offset(id, p_value);
- } else if (what == "material") {
- tile_set_material(id, p_value);
- } else if (what == "modulate") {
- tile_set_modulate(id, p_value);
- } else if (what == "region") {
- tile_set_region(id, p_value);
- } else if (what == "tile_mode") {
- tile_set_tile_mode(id, (TileMode)((int)p_value));
- } else if (what == "is_autotile") {
- // backward compatibility for Godot 3.0.x
- // autotile used to be a bool, it's now an enum
- bool is_autotile = p_value;
- if (is_autotile) {
- tile_set_tile_mode(id, AUTO_TILE);
- }
- } else if (what.left(9) == "autotile/") {
- what = what.right(9);
- if (what == "bitmask_mode") {
- autotile_set_bitmask_mode(id, (BitmaskMode)((int)p_value));
- } else if (what == "icon_coordinate") {
- autotile_set_icon_coordinate(id, p_value);
- } else if (what == "tile_size") {
- autotile_set_size(id, p_value);
- } else if (what == "spacing") {
- autotile_set_spacing(id, p_value);
- } else if (what == "bitmask_flags") {
- tile_map[id].autotile_data.flags.clear();
- if (p_value.is_array()) {
- Array p = p_value;
- Vector2 last_coord;
- while (p.size() > 0) {
- if (p[0].get_type() == Variant::VECTOR2) {
- last_coord = p[0];
- } else if (p[0].get_type() == Variant::INT) {
- autotile_set_bitmask(id, last_coord, p[0]);
- }
- p.pop_front();
- }
- }
- } else if (what == "occluder_map") {
- tile_map[id].autotile_data.occluder_map.clear();
- Array p = p_value;
- Vector2 last_coord;
- while (p.size() > 0) {
- if (p[0].get_type() == Variant::VECTOR2) {
- last_coord = p[0];
- } else if (p[0].get_type() == Variant::OBJECT) {
- autotile_set_light_occluder(id, p[0], last_coord);
- }
- p.pop_front();
- }
- } else if (what == "navpoly_map") {
- tile_map[id].autotile_data.navpoly_map.clear();
- Array p = p_value;
- Vector2 last_coord;
- while (p.size() > 0) {
- if (p[0].get_type() == Variant::VECTOR2) {
- last_coord = p[0];
- } else if (p[0].get_type() == Variant::OBJECT) {
- autotile_set_navigation_polygon(id, p[0], last_coord);
- }
- p.pop_front();
- }
- } else if (what == "priority_map") {
- tile_map[id].autotile_data.priority_map.clear();
- Array p = p_value;
- Vector3 val;
- Vector2 v;
- int priority;
- while (p.size() > 0) {
- val = p[0];
- if (val.z > 1) {
- v.x = val.x;
- v.y = val.y;
- priority = (int)val.z;
- tile_map[id].autotile_data.priority_map[v] = priority;
- }
- p.pop_front();
- }
- } else if (what == "z_index_map") {
- tile_map[id].autotile_data.z_index_map.clear();
- Array p = p_value;
- Vector3 val;
- Vector2 v;
- int z_index;
- while (p.size() > 0) {
- val = p[0];
- if (val.z != 0) {
- v.x = val.x;
- v.y = val.y;
- z_index = (int)val.z;
- tile_map[id].autotile_data.z_index_map[v] = z_index;
- }
- p.pop_front();
- }
- }
- } else if (what == "shape") {
- if (tile_get_shape_count(id) > 0) {
- for (int i = 0; i < tile_get_shape_count(id); i++) {
- tile_set_shape(id, i, p_value);
- }
- } else {
- tile_set_shape(id, 0, p_value);
- }
- } else if (what == "shape_offset") {
- if (tile_get_shape_count(id) > 0) {
- for (int i = 0; i < tile_get_shape_count(id); i++) {
- tile_set_shape_offset(id, i, p_value);
- }
- } else {
- tile_set_shape_offset(id, 0, p_value);
- }
- } else if (what == "shape_transform") {
- if (tile_get_shape_count(id) > 0) {
- for (int i = 0; i < tile_get_shape_count(id); i++) {
- tile_set_shape_transform(id, i, p_value);
- }
- } else {
- tile_set_shape_transform(id, 0, p_value);
- }
- } else if (what == "shape_one_way") {
- if (tile_get_shape_count(id) > 0) {
- for (int i = 0; i < tile_get_shape_count(id); i++) {
- tile_set_shape_one_way(id, i, p_value);
- }
- } else {
- tile_set_shape_one_way(id, 0, p_value);
- }
- } else if (what == "shape_one_way_margin") {
- if (tile_get_shape_count(id) > 0) {
- for (int i = 0; i < tile_get_shape_count(id); i++) {
- tile_set_shape_one_way_margin(id, i, p_value);
- }
- } else {
- tile_set_shape_one_way_margin(id, 0, p_value);
- }
- } else if (what == "shapes") {
- _tile_set_shapes(id, p_value);
- } else if (what == "occluder") {
- tile_set_light_occluder(id, p_value);
- } else if (what == "occluder_offset") {
- tile_set_occluder_offset(id, p_value);
- } else if (what == "navigation") {
- tile_set_navigation_polygon(id, p_value);
- } else if (what == "navigation_offset") {
- tile_set_navigation_polygon_offset(id, p_value);
- } else if (what == "z_index") {
- tile_set_z_index(id, p_value);
- } else {
- return false;
- }
+/////////////////////////////// TileSet //////////////////////////////////////
- return true;
+// --- Plugins ---
+Vector<TileSetPlugin *> TileSet::get_tile_set_atlas_plugins() const {
+ return tile_set_plugins_vector;
}
-bool TileSet::_get(const StringName &p_name, Variant &r_ret) const {
- String n = p_name;
- int slash = n.find("/");
- if (slash == -1) {
- return false;
+// -- Shape and layout --
+void TileSet::set_tile_shape(TileSet::TileShape p_shape) {
+ tile_shape = p_shape;
+
+ for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) {
+ E_source->get()->notify_tile_data_properties_should_change();
}
- int id = String::to_int(n.get_data(), slash);
-
- ERR_FAIL_COND_V(!tile_map.has(id), false);
-
- String what = n.substr(slash + 1, n.length());
-
- if (what == "name") {
- r_ret = tile_get_name(id);
- } else if (what == "texture") {
- r_ret = tile_get_texture(id);
- } else if (what == "tex_offset") {
- r_ret = tile_get_texture_offset(id);
- } else if (what == "material") {
- r_ret = tile_get_material(id);
- } else if (what == "modulate") {
- r_ret = tile_get_modulate(id);
- } else if (what == "region") {
- r_ret = tile_get_region(id);
- } else if (what == "tile_mode") {
- r_ret = tile_get_tile_mode(id);
- } else if (what.left(9) == "autotile/") {
- what = what.right(9);
- if (what == "bitmask_mode") {
- r_ret = autotile_get_bitmask_mode(id);
- } else if (what == "icon_coordinate") {
- r_ret = autotile_get_icon_coordinate(id);
- } else if (what == "tile_size") {
- r_ret = autotile_get_size(id);
- } else if (what == "spacing") {
- r_ret = autotile_get_spacing(id);
- } else if (what == "bitmask_flags") {
- Array p;
- for (Map<Vector2, uint32_t>::Element *E = tile_map[id].autotile_data.flags.front(); E; E = E->next()) {
- p.push_back(E->key());
- p.push_back(E->value());
- }
- r_ret = p;
- } else if (what == "occluder_map") {
- Array p;
- for (Map<Vector2, Ref<OccluderPolygon2D>>::Element *E = tile_map[id].autotile_data.occluder_map.front(); E; E = E->next()) {
- p.push_back(E->key());
- p.push_back(E->value());
- }
- r_ret = p;
- } else if (what == "navpoly_map") {
- Array p;
- for (Map<Vector2, Ref<NavigationPolygon>>::Element *E = tile_map[id].autotile_data.navpoly_map.front(); E; E = E->next()) {
- p.push_back(E->key());
- p.push_back(E->value());
- }
- r_ret = p;
- } else if (what == "priority_map") {
- Array p;
- Vector3 v;
- for (Map<Vector2, int>::Element *E = tile_map[id].autotile_data.priority_map.front(); E; E = E->next()) {
- if (E->value() > 1) {
- //Don't save default value
- v.x = E->key().x;
- v.y = E->key().y;
- v.z = E->value();
- p.push_back(v);
- }
- }
- r_ret = p;
- } else if (what == "z_index_map") {
- Array p;
- Vector3 v;
- for (Map<Vector2, int>::Element *E = tile_map[id].autotile_data.z_index_map.front(); E; E = E->next()) {
- if (E->value() != 0) {
- //Don't save default value
- v.x = E->key().x;
- v.y = E->key().y;
- v.z = E->value();
- p.push_back(v);
- }
- }
- r_ret = p;
- }
- } else if (what == "shape") {
- r_ret = tile_get_shape(id, 0);
- } else if (what == "shape_offset") {
- r_ret = tile_get_shape_offset(id, 0);
- } else if (what == "shape_transform") {
- r_ret = tile_get_shape_transform(id, 0);
- } else if (what == "shape_one_way") {
- r_ret = tile_get_shape_one_way(id, 0);
- } else if (what == "shape_one_way_margin") {
- r_ret = tile_get_shape_one_way_margin(id, 0);
- } else if (what == "shapes") {
- r_ret = _tile_get_shapes(id);
- } else if (what == "occluder") {
- r_ret = tile_get_light_occluder(id);
- } else if (what == "occluder_offset") {
- r_ret = tile_get_occluder_offset(id);
- } else if (what == "navigation") {
- r_ret = tile_get_navigation_polygon(id);
- } else if (what == "navigation_offset") {
- r_ret = tile_get_navigation_polygon_offset(id);
- } else if (what == "z_index") {
- r_ret = tile_get_z_index(id);
- } else {
- return false;
+
+ emit_changed();
+}
+TileSet::TileShape TileSet::get_tile_shape() const {
+ return tile_shape;
+}
+
+void TileSet::set_tile_layout(TileSet::TileLayout p_layout) {
+ tile_layout = p_layout;
+ emit_changed();
+}
+TileSet::TileLayout TileSet::get_tile_layout() const {
+ return tile_layout;
+}
+
+void TileSet::set_tile_offset_axis(TileSet::TileOffsetAxis p_alignment) {
+ tile_offset_axis = p_alignment;
+
+ for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) {
+ E_source->get()->notify_tile_data_properties_should_change();
}
- return true;
+ emit_changed();
+}
+TileSet::TileOffsetAxis TileSet::get_tile_offset_axis() const {
+ return tile_offset_axis;
}
-void TileSet::_get_property_list(List<PropertyInfo> *p_list) const {
- for (Map<int, TileData>::Element *E = tile_map.front(); E; E = E->next()) {
- int id = E->key();
- String pre = itos(id) + "/";
- p_list->push_back(PropertyInfo(Variant::STRING, pre + "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "tex_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::COLOR, pre + "modulate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::RECT2, pre + "region", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::INT, pre + "tile_mode", PROPERTY_HINT_ENUM, "SINGLE_TILE,AUTO_TILE,ATLAS_TILE", PROPERTY_USAGE_NOEDITOR));
- if (tile_get_tile_mode(id) == AUTO_TILE) {
- p_list->push_back(PropertyInfo(Variant::INT, pre + "autotile/bitmask_mode", PROPERTY_HINT_ENUM, "2X2,3X3 (minimal),3X3", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/bitmask_flags", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "autotile/icon_coordinate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "autotile/tile_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::INT, pre + "autotile/spacing", PROPERTY_HINT_RANGE, "0,256,1", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/occluder_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/navpoly_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/priority_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/z_index_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- } else if (tile_get_tile_mode(id) == ATLAS_TILE) {
- p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "autotile/icon_coordinate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "autotile/tile_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::INT, pre + "autotile/spacing", PROPERTY_HINT_RANGE, "0,256,1", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/occluder_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/navpoly_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/priority_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/z_index_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- }
- p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "occluder_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "occluder", PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "navigation_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "navigation", PROPERTY_HINT_RESOURCE_TYPE, "NavigationPolygon", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "shape_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "shape_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape2D", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::BOOL, pre + "shape_one_way", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::FLOAT, pre + "shape_one_way_margin", PROPERTY_HINT_RANGE, "0,128,0.01", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "shapes", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::INT, pre + "z_index", PROPERTY_HINT_RANGE, itos(RS::CANVAS_ITEM_Z_MIN) + "," + itos(RS::CANVAS_ITEM_Z_MAX) + ",1", PROPERTY_USAGE_NOEDITOR));
- }
-}
-
-void TileSet::create_tile(int p_id) {
- ERR_FAIL_COND(tile_map.has(p_id));
- tile_map[p_id] = TileData();
- tile_map[p_id].autotile_data = AutotileData();
- notify_property_list_changed();
+void TileSet::set_tile_size(Size2i p_size) {
+ ERR_FAIL_COND(p_size.x < 1 || p_size.y < 1);
+ tile_size = p_size;
emit_changed();
}
+Size2i TileSet::get_tile_size() const {
+ return tile_size;
+}
-void TileSet::autotile_set_bitmask_mode(int p_id, BitmaskMode p_mode) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].autotile_data.bitmask_mode = p_mode;
- notify_property_list_changed();
+void TileSet::set_tile_skew(Vector2 p_skew) {
emit_changed();
+ tile_skew = p_skew;
+}
+Vector2 TileSet::get_tile_skew() const {
+ return tile_skew;
+}
+
+int TileSet::get_next_source_id() const {
+ return next_source_id;
}
-TileSet::BitmaskMode TileSet::autotile_get_bitmask_mode(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), BITMASK_2X2);
- return tile_map[p_id].autotile_data.bitmask_mode;
+void TileSet::_compute_next_source_id() {
+ while (sources.has(next_source_id)) {
+ next_source_id = (next_source_id + 1) % 1073741824; // 2 ** 30
+ };
}
-void TileSet::tile_set_texture(int p_id, const Ref<Texture2D> &p_texture) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].texture = p_texture;
+// 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);
+ 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;
+ source_ids.append(new_source_id);
+ source_ids.sort();
+ p_tile_atlas_source->set_tile_set(this);
+ _compute_next_source_id();
+
+ sources[new_source_id]->connect("changed", callable_mp(this, &TileSet::_source_changed));
+
emit_changed();
+
+ return new_source_id;
}
-Ref<Texture2D> TileSet::tile_get_texture(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Ref<Texture2D>());
- return tile_map[p_id].texture;
+void TileSet::remove_source(int p_source_id) {
+ ERR_FAIL_COND_MSG(!sources.has(p_source_id), vformat("Cannot remove TileSet atlas source. No tileset atlas source with id %d.", p_source_id));
+
+ sources[p_source_id]->disconnect("changed", callable_mp(this, &TileSet::_source_changed));
+
+ sources[p_source_id]->set_tile_set(nullptr);
+ sources.erase(p_source_id);
+ source_ids.erase(p_source_id);
+ source_ids.sort();
+
+ emit_changed();
}
-void TileSet::tile_set_material(int p_id, const Ref<ShaderMaterial> &p_material) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].material = p_material;
+void TileSet::set_source_id(int p_source_id, int p_new_source_id) {
+ ERR_FAIL_COND(p_new_source_id < 0);
+ ERR_FAIL_COND_MSG(!sources.has(p_source_id), vformat("Cannot change TileSet atlas source ID. No tileset atlas source with id %d.", p_source_id));
+ if (p_source_id == p_new_source_id) {
+ return;
+ }
+
+ ERR_FAIL_COND_MSG(sources.has(p_new_source_id), vformat("Cannot change TileSet atlas source ID. Another atlas source exists with id %d.", p_new_source_id));
+
+ sources[p_new_source_id] = sources[p_source_id];
+ sources.erase(p_source_id);
+
+ source_ids.erase(p_source_id);
+ source_ids.append(p_new_source_id);
+ source_ids.sort();
+
emit_changed();
}
-Ref<ShaderMaterial> TileSet::tile_get_material(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Ref<ShaderMaterial>());
- return tile_map[p_id].material;
+bool TileSet::has_source(int p_source_id) const {
+ return sources.has(p_source_id);
+}
+
+Ref<TileSetSource> TileSet::get_source(int p_source_id) const {
+ ERR_FAIL_COND_V_MSG(!sources.has(p_source_id), nullptr, vformat("No TileSet atlas source with id %d.", p_source_id));
+
+ return sources[p_source_id];
+}
+
+int TileSet::get_source_count() const {
+ return source_ids.size();
}
-void TileSet::tile_set_modulate(int p_id, const Color &p_modulate) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].modulate = p_modulate;
+int TileSet::get_source_id(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, source_ids.size(), -1);
+ return source_ids[p_index];
+}
+
+// Rendering
+void TileSet::set_uv_clipping(bool p_uv_clipping) {
+ if (uv_clipping == p_uv_clipping) {
+ return;
+ }
+ uv_clipping = p_uv_clipping;
emit_changed();
}
+bool TileSet::is_uv_clipping() const {
+ return uv_clipping;
+};
-Color TileSet::tile_get_modulate(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Color(1, 1, 1));
- return tile_map[p_id].modulate;
+void TileSet::set_y_sorting(bool p_y_sort) {
+ if (y_sorting == p_y_sort) {
+ return;
+ }
+ y_sorting = p_y_sort;
+ emit_changed();
}
+bool TileSet::is_y_sorting() const {
+ return y_sorting;
+};
-void TileSet::tile_set_texture_offset(int p_id, const Vector2 &p_offset) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].offset = p_offset;
+void TileSet::set_occlusion_layers_count(int p_occlusion_layers_count) {
+ ERR_FAIL_COND(p_occlusion_layers_count < 0);
+ if (occlusion_layers.size() == p_occlusion_layers_count) {
+ return;
+ }
+
+ occlusion_layers.resize(p_occlusion_layers_count);
+
+ for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) {
+ E_source->get()->notify_tile_data_properties_should_change();
+ }
+
+ notify_property_list_changed();
emit_changed();
}
-Vector2 TileSet::tile_get_texture_offset(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Vector2());
- return tile_map[p_id].offset;
+int TileSet::get_occlusion_layers_count() const {
+ return occlusion_layers.size();
+};
+
+void TileSet::set_occlusion_layer_light_mask(int p_layer_index, int p_light_mask) {
+ ERR_FAIL_INDEX(p_layer_index, occlusion_layers.size());
+ occlusion_layers.write[p_layer_index].light_mask = p_light_mask;
+ emit_changed();
+}
+
+int TileSet::get_occlusion_layer_light_mask(int p_layer_index) const {
+ ERR_FAIL_INDEX_V(p_layer_index, occlusion_layers.size(), 0);
+ return occlusion_layers[p_layer_index].light_mask;
}
-void TileSet::tile_set_region(int p_id, const Rect2 &p_region) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].region = p_region;
+void TileSet::set_occlusion_layer_sdf_collision(int p_layer_index, int p_sdf_collision) {
+ ERR_FAIL_INDEX(p_layer_index, occlusion_layers.size());
+ occlusion_layers.write[p_layer_index].sdf_collision = p_sdf_collision;
emit_changed();
}
-Rect2 TileSet::tile_get_region(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Rect2());
- return tile_map[p_id].region;
+bool TileSet::get_occlusion_layer_sdf_collision(int p_layer_index) const {
+ ERR_FAIL_INDEX_V(p_layer_index, occlusion_layers.size(), false);
+ return occlusion_layers[p_layer_index].sdf_collision;
+}
+
+void TileSet::draw_tile_shape(CanvasItem *p_canvas_item, Rect2 p_region, Color p_color, bool p_filled, Ref<Texture2D> p_texture) {
+ // TODO: optimize this with 2D meshes when they work again.
+ if (get_tile_shape() == TileSet::TILE_SHAPE_SQUARE) {
+ if (p_filled && p_texture.is_valid()) {
+ p_canvas_item->draw_texture_rect(p_texture, p_region, false, p_color);
+ } else {
+ p_canvas_item->draw_rect(p_region, p_color, p_filled);
+ }
+ } else {
+ float overlap = 0.0;
+ switch (get_tile_shape()) {
+ case TileSet::TILE_SHAPE_ISOMETRIC:
+ overlap = 0.5;
+ break;
+ case TileSet::TILE_SHAPE_HEXAGON:
+ overlap = 0.25;
+ break;
+ case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE:
+ overlap = 0.0;
+ break;
+ default:
+ break;
+ }
+
+ Vector<Vector2> uvs;
+ uvs.append(Vector2(0.5, 0.0));
+ uvs.append(Vector2(0.0, overlap));
+ uvs.append(Vector2(0.0, 1.0 - overlap));
+ uvs.append(Vector2(0.5, 1.0));
+ uvs.append(Vector2(1.0, 1.0 - overlap));
+ uvs.append(Vector2(1.0, overlap));
+ uvs.append(Vector2(0.5, 0.0));
+ if (get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL) {
+ for (int i = 0; i < uvs.size(); i++) {
+ uvs.write[i] = Vector2(uvs[i].y, uvs[i].x);
+ }
+ }
+
+ Vector<Vector2> points;
+ for (int i = 0; i < uvs.size(); i++) {
+ points.append(p_region.position + uvs[i] * p_region.size);
+ }
+
+ if (p_filled) {
+ // This does hurt performances a lot. We should use a mesh if possible instead.
+ p_canvas_item->draw_colored_polygon(points, p_color, uvs, p_texture);
+
+ // Should improve performances, but does not work as draw_primitive does not work with textures :/ :
+ /*for (int i = 0; i < 6; i += 3) {
+ Vector<Vector2> quad;
+ quad.append(points[i]);
+ quad.append(points[(i + 1) % points.size()]);
+ quad.append(points[(i + 2) % points.size()]);
+ quad.append(points[(i + 3) % points.size()]);
+
+ Vector<Vector2> uv_quad;
+ uv_quad.append(uvs[i]);
+ uv_quad.append(uvs[(i + 1) % uvs.size()]);
+ uv_quad.append(uvs[(i + 2) % uvs.size()]);
+ uv_quad.append(uvs[(i + 3) % uvs.size()]);
+
+ p_control->draw_primitive(quad, Vector<Color>(), uv_quad, p_texture);
+ }*/
+
+ } else {
+ // This does hurt performances a lot. We should use a mesh if possible instead.
+ // tile_shape_grid->draw_polyline(points, p_color);
+ for (int i = 0; i < points.size() - 1; i++) {
+ p_canvas_item->draw_line(points[i], points[i + 1], p_color);
+ }
+ }
+ }
}
-void TileSet::tile_set_tile_mode(int p_id, TileMode p_tile_mode) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].tile_mode = p_tile_mode;
+// Physics
+void TileSet::set_physics_layers_count(int p_physics_layers_count) {
+ ERR_FAIL_COND(p_physics_layers_count < 0);
+ if (physics_layers.size() == p_physics_layers_count) {
+ return;
+ }
+
+ physics_layers.resize(p_physics_layers_count);
+
+ for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) {
+ E_source->get()->notify_tile_data_properties_should_change();
+ }
+
+ notify_property_list_changed();
emit_changed();
}
-TileSet::TileMode TileSet::tile_get_tile_mode(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), SINGLE_TILE);
- return tile_map[p_id].tile_mode;
+int TileSet::get_physics_layers_count() const {
+ return physics_layers.size();
}
-void TileSet::autotile_set_icon_coordinate(int p_id, Vector2 coord) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].autotile_data.icon_coord = coord;
+void TileSet::set_physics_layer_collision_layer(int p_layer_index, uint32_t p_layer) {
+ ERR_FAIL_INDEX(p_layer_index, physics_layers.size());
+ physics_layers.write[p_layer_index].collision_layer = p_layer;
emit_changed();
}
-Vector2 TileSet::autotile_get_icon_coordinate(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Vector2());
- return tile_map[p_id].autotile_data.icon_coord;
+uint32_t TileSet::get_physics_layer_collision_layer(int p_layer_index) const {
+ ERR_FAIL_INDEX_V(p_layer_index, physics_layers.size(), 0);
+ return physics_layers[p_layer_index].collision_layer;
}
-void TileSet::autotile_set_spacing(int p_id, int p_spacing) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- ERR_FAIL_COND(p_spacing < 0);
- tile_map[p_id].autotile_data.spacing = p_spacing;
+void TileSet::set_physics_layer_collision_mask(int p_layer_index, uint32_t p_mask) {
+ ERR_FAIL_INDEX(p_layer_index, physics_layers.size());
+ physics_layers.write[p_layer_index].collision_mask = p_mask;
emit_changed();
}
-int TileSet::autotile_get_spacing(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), 0);
- return tile_map[p_id].autotile_data.spacing;
+uint32_t TileSet::get_physics_layer_collision_mask(int p_layer_index) const {
+ ERR_FAIL_INDEX_V(p_layer_index, physics_layers.size(), 0);
+ return physics_layers[p_layer_index].collision_mask;
}
-void TileSet::autotile_set_size(int p_id, Size2 p_size) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- ERR_FAIL_COND(p_size.x <= 0 || p_size.y <= 0);
- tile_map[p_id].autotile_data.size = p_size;
+void TileSet::set_physics_layer_physics_material(int p_layer_index, Ref<PhysicsMaterial> p_physics_material) {
+ ERR_FAIL_INDEX(p_layer_index, physics_layers.size());
+ physics_layers.write[p_layer_index].physics_material = p_physics_material;
}
-Size2 TileSet::autotile_get_size(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Size2());
- return tile_map[p_id].autotile_data.size;
+Ref<PhysicsMaterial> TileSet::get_physics_layer_physics_material(int p_layer_index) const {
+ ERR_FAIL_INDEX_V(p_layer_index, physics_layers.size(), Ref<PhysicsMaterial>());
+ return physics_layers[p_layer_index].physics_material;
}
-void TileSet::autotile_clear_bitmask_map(int p_id) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].autotile_data.flags.clear();
+// Terrains
+void TileSet::set_terrain_sets_count(int p_terrains_sets_count) {
+ ERR_FAIL_COND(p_terrains_sets_count < 0);
+
+ terrain_sets.resize(p_terrains_sets_count);
+
+ notify_property_list_changed();
+ emit_changed();
}
-void TileSet::autotile_set_subtile_priority(int p_id, const Vector2 &p_coord, int p_priority) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- ERR_FAIL_COND(p_priority <= 0);
- tile_map[p_id].autotile_data.priority_map[p_coord] = p_priority;
+int TileSet::get_terrain_sets_count() const {
+ return terrain_sets.size();
}
-int TileSet::autotile_get_subtile_priority(int p_id, const Vector2 &p_coord) {
- ERR_FAIL_COND_V(!tile_map.has(p_id), 1);
- if (tile_map[p_id].autotile_data.priority_map.has(p_coord)) {
- return tile_map[p_id].autotile_data.priority_map[p_coord];
+void TileSet::set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode) {
+ ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size());
+ terrain_sets.write[p_terrain_set].mode = p_terrain_mode;
+ for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) {
+ E_source->get()->notify_tile_data_properties_should_change();
}
- //When not custom priority set return the default value
- return 1;
+
+ notify_property_list_changed();
+ emit_changed();
}
-const Map<Vector2, int> &TileSet::autotile_get_priority_map(int p_id) const {
- static Map<Vector2, int> dummy;
- ERR_FAIL_COND_V(!tile_map.has(p_id), dummy);
- return tile_map[p_id].autotile_data.priority_map;
+TileSet::TerrainMode TileSet::get_terrain_set_mode(int p_terrain_set) const {
+ ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES);
+ return terrain_sets[p_terrain_set].mode;
}
-void TileSet::autotile_set_z_index(int p_id, const Vector2 &p_coord, int p_z_index) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].autotile_data.z_index_map[p_coord] = p_z_index;
+void TileSet::set_terrains_count(int p_terrain_set, int p_terrains_layers_count) {
+ ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size());
+ ERR_FAIL_COND(p_terrains_layers_count < 0);
+ if (terrain_sets[p_terrain_set].terrains.size() == p_terrains_layers_count) {
+ return;
+ }
+
+ int old_size = terrain_sets[p_terrain_set].terrains.size();
+ terrain_sets.write[p_terrain_set].terrains.resize(p_terrains_layers_count);
+
+ // Default name and color
+ for (int i = old_size; i < terrain_sets.write[p_terrain_set].terrains.size(); i++) {
+ float hue_rotate = (i * 2 % 16) / 16.0;
+ Color c;
+ c.set_hsv(Math::fmod(float(hue_rotate), float(1.0)), 0.5, 0.5);
+ terrain_sets.write[p_terrain_set].terrains.write[i].color = c;
+ terrain_sets.write[p_terrain_set].terrains.write[i].name = String(vformat("Terrain %d", i));
+ }
+
+ for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) {
+ E_source->get()->notify_tile_data_properties_should_change();
+ }
+
+ notify_property_list_changed();
emit_changed();
}
-int TileSet::autotile_get_z_index(int p_id, const Vector2 &p_coord) {
- ERR_FAIL_COND_V(!tile_map.has(p_id), 1);
- if (tile_map[p_id].autotile_data.z_index_map.has(p_coord)) {
- return tile_map[p_id].autotile_data.z_index_map[p_coord];
+int TileSet::get_terrains_count(int p_terrain_set) const {
+ ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), -1);
+ return terrain_sets[p_terrain_set].terrains.size();
+}
+
+void TileSet::set_terrain_name(int p_terrain_set, int p_terrain_index, String p_name) {
+ ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size());
+ ERR_FAIL_INDEX(p_terrain_index, terrain_sets[p_terrain_set].terrains.size());
+ terrain_sets.write[p_terrain_set].terrains.write[p_terrain_index].name = p_name;
+ emit_changed();
+}
+
+String TileSet::get_terrain_name(int p_terrain_set, int p_terrain_index) const {
+ ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), String());
+ ERR_FAIL_INDEX_V(p_terrain_index, terrain_sets[p_terrain_set].terrains.size(), String());
+ return terrain_sets[p_terrain_set].terrains[p_terrain_index].name;
+}
+
+void TileSet::set_terrain_color(int p_terrain_set, int p_terrain_index, Color p_color) {
+ ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size());
+ ERR_FAIL_INDEX(p_terrain_index, terrain_sets[p_terrain_set].terrains.size());
+ if (p_color.a != 1.0) {
+ WARN_PRINT("Terrain color should have alpha == 1.0");
+ p_color.a = 1.0;
}
- //When not custom z index set return the default value
- return 0;
+ terrain_sets.write[p_terrain_set].terrains.write[p_terrain_index].color = p_color;
+ emit_changed();
}
-const Map<Vector2, int> &TileSet::autotile_get_z_index_map(int p_id) const {
- static Map<Vector2, int> dummy;
- ERR_FAIL_COND_V(!tile_map.has(p_id), dummy);
- return tile_map[p_id].autotile_data.z_index_map;
+Color TileSet::get_terrain_color(int p_terrain_set, int p_terrain_index) const {
+ ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), Color());
+ ERR_FAIL_INDEX_V(p_terrain_index, terrain_sets[p_terrain_set].terrains.size(), Color());
+ return terrain_sets[p_terrain_set].terrains[p_terrain_index].color;
}
-void TileSet::autotile_set_bitmask(int p_id, Vector2 p_coord, uint32_t p_flag) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- if (p_flag == 0) {
- if (tile_map[p_id].autotile_data.flags.has(p_coord)) {
- tile_map[p_id].autotile_data.flags.erase(p_coord);
+bool TileSet::is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const {
+ if (p_terrain_set < 0 || p_terrain_set >= get_terrain_sets_count()) {
+ return false;
+ }
+
+ TileSet::TerrainMode terrain_mode = get_terrain_set_mode(p_terrain_set);
+ if (tile_shape == TileSet::TILE_SHAPE_SQUARE) {
+ if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) {
+ if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_SIDE ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_SIDE ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_SIDE) {
+ return true;
+ }
+ }
+ if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) {
+ if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER) {
+ return true;
+ }
+ }
+ } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) {
+ if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
+ return true;
+ }
+ }
+ if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) {
+ if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_CORNER ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_CORNER ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_CORNER) {
+ return true;
+ }
}
} else {
- tile_map[p_id].autotile_data.flags[p_coord] = p_flag;
+ if (get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) {
+ if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_SIDE ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_SIDE ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
+ return true;
+ }
+ }
+ if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) {
+ if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_CORNER ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER) {
+ return true;
+ }
+ }
+ } else {
+ if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) {
+ if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_SIDE ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
+ return true;
+ }
+ }
+ if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) {
+ if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_CORNER ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_CORNER ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER ||
+ p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+// Navigation
+void TileSet::set_navigation_layers_count(int p_navigation_layers_count) {
+ ERR_FAIL_COND(p_navigation_layers_count < 0);
+ if (navigation_layers.size() == p_navigation_layers_count) {
+ return;
+ }
+
+ navigation_layers.resize(p_navigation_layers_count);
+
+ for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) {
+ E_source->get()->notify_tile_data_properties_should_change();
}
+
+ notify_property_list_changed();
+ emit_changed();
+}
+
+int TileSet::get_navigation_layers_count() const {
+ return navigation_layers.size();
+}
+
+void TileSet::set_navigation_layer_layers(int p_layer_index, uint32_t p_layers) {
+ ERR_FAIL_INDEX(p_layer_index, navigation_layers.size());
+ navigation_layers.write[p_layer_index].layers = p_layers;
+ emit_changed();
}
-uint32_t TileSet::autotile_get_bitmask(int p_id, Vector2 p_coord) {
- ERR_FAIL_COND_V(!tile_map.has(p_id), 0);
- if (!tile_map[p_id].autotile_data.flags.has(p_coord)) {
- return 0;
+uint32_t TileSet::get_navigation_layer_layers(int p_layer_index) const {
+ ERR_FAIL_INDEX_V(p_layer_index, navigation_layers.size(), 0);
+ return navigation_layers[p_layer_index].layers;
+}
+
+// Custom data.
+void TileSet::set_custom_data_layers_count(int p_custom_data_layers_count) {
+ ERR_FAIL_COND(p_custom_data_layers_count < 0);
+ if (custom_data_layers.size() == p_custom_data_layers_count) {
+ return;
}
- return tile_map[p_id].autotile_data.flags[p_coord];
+
+ custom_data_layers.resize(p_custom_data_layers_count);
+
+ for (Map<String, int>::Element *E = custom_data_layers_by_name.front(); E; E = E->next()) {
+ if (E->get() >= custom_data_layers.size()) {
+ custom_data_layers_by_name.erase(E);
+ }
+ }
+
+ for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) {
+ E_source->get()->notify_tile_data_properties_should_change();
+ }
+
+ notify_property_list_changed();
+ emit_changed();
}
-const Map<Vector2, uint32_t> &TileSet::autotile_get_bitmask_map(int p_id) {
- static Map<Vector2, uint32_t> dummy;
- static Map<Vector2, uint32_t> dummy_atlas;
- ERR_FAIL_COND_V(!tile_map.has(p_id), dummy);
- if (tile_get_tile_mode(p_id) == ATLAS_TILE) {
- dummy_atlas = Map<Vector2, uint32_t>();
- Rect2 region = tile_get_region(p_id);
- Size2 size = autotile_get_size(p_id);
- float spacing = autotile_get_spacing(p_id);
- for (int x = 0; x < (region.size.x / (size.x + spacing)); x++) {
- for (int y = 0; y < (region.size.y / (size.y + spacing)); y++) {
- dummy_atlas.insert(Vector2(x, y), 0);
+int TileSet::get_custom_data_layers_count() const {
+ return custom_data_layers.size();
+}
+
+int TileSet::get_custom_data_layer_by_name(String p_value) const {
+ if (custom_data_layers_by_name.has(p_value)) {
+ return custom_data_layers_by_name[p_value];
+ } else {
+ return -1;
+ }
+}
+
+void TileSet::set_custom_data_name(int p_layer_id, String p_value) {
+ ERR_FAIL_INDEX(p_layer_id, custom_data_layers.size());
+
+ // Exit if another property has the same name.
+ if (!p_value.is_empty()) {
+ for (int other_layer_id = 0; other_layer_id < get_custom_data_layers_count(); other_layer_id++) {
+ if (other_layer_id != p_layer_id && get_custom_data_name(other_layer_id) == p_value) {
+ ERR_FAIL_MSG(vformat("There is already a custom property named %s", p_value));
}
}
- return dummy_atlas;
+ }
+
+ if (p_value.is_empty() && custom_data_layers_by_name.has(p_value)) {
+ custom_data_layers_by_name.erase(p_value);
} else {
- return tile_map[p_id].autotile_data.flags;
+ custom_data_layers_by_name[p_value] = p_layer_id;
}
+
+ custom_data_layers.write[p_layer_id].name = p_value;
+ emit_changed();
+}
+
+String TileSet::get_custom_data_name(int p_layer_id) const {
+ ERR_FAIL_INDEX_V(p_layer_id, custom_data_layers.size(), "");
+ return custom_data_layers[p_layer_id].name;
+}
+
+void TileSet::set_custom_data_type(int p_layer_id, Variant::Type p_value) {
+ ERR_FAIL_INDEX(p_layer_id, custom_data_layers.size());
+ custom_data_layers.write[p_layer_id].type = p_value;
+
+ for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) {
+ E_source->get()->notify_tile_data_properties_should_change();
+ }
+
+ emit_changed();
+}
+
+Variant::Type TileSet::get_custom_data_type(int p_layer_id) const {
+ ERR_FAIL_INDEX_V(p_layer_id, custom_data_layers.size(), Variant::NIL);
+ return custom_data_layers[p_layer_id].type;
+}
+
+void TileSet::_source_changed() {
+ emit_changed();
+ notify_property_list_changed();
+}
+
+void TileSet::reset_state() {
+ occlusion_layers.clear();
+ physics_layers.clear();
+ custom_data_layers.clear();
}
-Vector2 TileSet::autotile_get_subtile_for_bitmask(int p_id, uint16_t p_bitmask, const Node *p_tilemap_node, const Vector2 &p_tile_location) {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Vector2());
- //First try to forward selection to script
- if (p_tilemap_node->get_class_name() == "TileMap") {
- if (get_script_instance() != nullptr) {
- if (get_script_instance()->has_method("_forward_subtile_selection")) {
- Variant ret = get_script_instance()->call("_forward_subtile_selection", p_id, p_bitmask, p_tilemap_node, p_tile_location);
- if (ret.get_type() == Variant::VECTOR2) {
- return ret;
+const Vector2i TileSetAtlasSource::INVALID_ATLAS_COORDS = Vector2i(-1, -1);
+const int TileSetAtlasSource::INVALID_TILE_ALTERNATIVE = -1;
+
+#ifndef DISABLE_DEPRECATED
+void TileSet::compatibility_conversion() {
+ for (Map<int, CompatibilityTileData *>::Element *E = compatibility_data.front(); E; E = E->next()) {
+ CompatibilityTileData *ctd = E->value();
+
+ // Add the texture
+ TileSetAtlasSource *atlas_source = memnew(TileSetAtlasSource);
+ int source_id = add_source(Ref<TileSetSource>(atlas_source));
+
+ atlas_source->set_texture(ctd->texture);
+
+ // Handle each tile as a new source. Not optimal but at least it should stay compatible.
+ switch (ctd->tile_mode) {
+ case 0: // SINGLE_TILE
+ // TODO
+ break;
+ case 1: // AUTO_TILE
+ // TODO
+ break;
+ case 2: // ATLAS_TILE
+ atlas_source->set_margins(ctd->region.get_position());
+ atlas_source->set_separation(Vector2i(ctd->autotile_spacing, ctd->autotile_spacing));
+ atlas_source->set_texture_region_size(ctd->autotile_tile_size);
+
+ Size2i atlas_size = ctd->region.get_size() / (ctd->autotile_tile_size + atlas_source->get_separation());
+ for (int i = 0; i < atlas_size.x; i++) {
+ for (int j = 0; j < atlas_size.y; j++) {
+ Vector2i coords = Vector2i(i, j);
+
+ for (int flags = 0; flags < 8; flags++) {
+ bool flip_h = flags & 1;
+ bool flip_v = flags & 2;
+ bool transpose = flags & 4;
+
+ int alternative_tile = 0;
+ if (!atlas_source->has_tile(coords)) {
+ atlas_source->create_tile(coords);
+ } else {
+ alternative_tile = atlas_source->create_alternative_tile(coords);
+ }
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(coords, alternative_tile));
+
+ tile_data->set_flip_h(flip_h);
+ tile_data->set_flip_v(flip_v);
+ tile_data->set_transpose(transpose);
+ tile_data->tile_set_material(ctd->material);
+ tile_data->set_modulate(ctd->modulate);
+ tile_data->set_z_index(ctd->z_index);
+ if (ctd->autotile_occluder_map.has(coords)) {
+ if (get_occlusion_layers_count() < 1) {
+ set_occlusion_layers_count(1);
+ }
+ tile_data->set_occluder(0, ctd->autotile_occluder_map[coords]);
+ }
+ if (ctd->autotile_navpoly_map.has(coords)) {
+ if (get_navigation_layers_count() < 1) {
+ set_navigation_layers_count(1);
+ }
+ tile_data->set_navigation_polygon(0, ctd->autotile_navpoly_map[coords]);
+ }
+ if (ctd->autotile_priority_map.has(coords)) {
+ tile_data->set_probability(ctd->autotile_priority_map[coords]);
+ }
+ if (ctd->autotile_z_index_map.has(coords)) {
+ tile_data->set_z_index(ctd->autotile_z_index_map[coords]);
+ }
+
+ // Add the shapes.
+ if (ctd->shapes.size() > 0) {
+ if (get_physics_layers_count() < 1) {
+ set_physics_layers_count(1);
+ }
+ }
+ for (int k = 0; k < ctd->shapes.size(); k++) {
+ CompatibilityShapeData csd = ctd->shapes[k];
+ if (csd.autotile_coords == coords) {
+ tile_data->set_collision_shapes_count(0, tile_data->get_collision_shapes_count(0) + 1);
+ int index = tile_data->get_collision_shapes_count(0) - 1;
+ tile_data->set_collision_shape_one_way(0, index, csd.one_way);
+ tile_data->set_collision_shape_one_way_margin(0, index, csd.one_way_margin);
+ tile_data->set_collision_shape_shape(0, index, csd.shape);
+ // Ignores transform for now.
+ }
+ }
+
+ // -- TODO: handle --
+ // Those are offset for the whole atlas, they are likely useless for the atlases, but might make sense for single tiles.
+ // texture offset
+ // occluder_offset
+ // navigation_offset
+
+ // For terrains, ignored for now?
+ // bitmask_mode
+ // bitmask_flags
+ }
+ }
+ }
+ break;
+ }
+
+ // Offset all shapes
+ for (int k = 0; k < ctd->shapes.size(); k++) {
+ Ref<ConvexPolygonShape2D> convex = ctd->shapes[k].shape;
+ if (convex.is_valid()) {
+ Vector<Vector2> points = convex->get_points();
+ for (int i_point = 0; i_point < points.size(); i_point++) {
+ points.write[i_point] = points[i_point] - get_tile_size() / 2;
}
+ convex->set_points(points);
}
}
+
+ // Add the mapping to the map
+ compatibility_source_mapping.insert(E->key(), source_id);
+ }
+
+ // Reset compatibility data
+ for (Map<int, CompatibilityTileData *>::Element *E = compatibility_data.front(); E; E = E->next()) {
+ memdelete(E->get());
}
+ compatibility_data = Map<int, CompatibilityTileData *>();
+}
+#endif // DISABLE_DEPRECATED
- List<Vector2> coords;
- List<uint32_t> priorities;
- uint32_t priority_sum = 0;
- uint32_t mask;
- uint16_t mask_;
- uint16_t mask_ignore;
- for (Map<Vector2, uint32_t>::Element *E = tile_map[p_id].autotile_data.flags.front(); E; E = E->next()) {
- mask = E->get();
- if (tile_map[p_id].autotile_data.bitmask_mode == BITMASK_2X2) {
- mask |= (BIND_IGNORE_TOP | BIND_IGNORE_LEFT | BIND_IGNORE_CENTER | BIND_IGNORE_RIGHT | BIND_IGNORE_BOTTOM);
+bool TileSet::_set(const StringName &p_name, const Variant &p_value) {
+ Vector<String> components = String(p_name).split("/", true, 2);
+
+#ifndef DISABLE_DEPRECATED
+ // TODO: THIS IS HOW WE CHECK IF WE HAVE A DEPRECATED RESOURCE
+ // This should be moved to a dedicated conversion system
+ if (components.size() >= 1 && components[0].is_valid_integer()) {
+ int id = components[0].to_int();
+
+ // Get or create the compatibility object
+ CompatibilityTileData *ctd;
+ Map<int, CompatibilityTileData *>::Element *E = compatibility_data.find(id);
+ if (!E) {
+ ctd = memnew(CompatibilityTileData);
+ compatibility_data.insert(id, ctd);
+ } else {
+ ctd = E->get();
}
- mask_ = mask & 0xFFFF;
- mask_ignore = mask >> 16;
+ if (components.size() < 2) {
+ return false;
+ }
- if (((mask_ & (~mask_ignore)) == (p_bitmask & (~mask_ignore))) && (((~mask_) | mask_ignore) == ((~p_bitmask) | mask_ignore))) {
- uint32_t priority = autotile_get_subtile_priority(p_id, E->key());
- priority_sum += priority;
- priorities.push_back(priority);
- coords.push_back(E->key());
+ String what = components[1];
+
+ if (what == "name") {
+ ctd->name = p_value;
+ } else if (what == "texture") {
+ ctd->texture = p_value;
+ } else if (what == "tex_offset") {
+ ctd->tex_offset = p_value;
+ } else if (what == "material") {
+ ctd->material = p_value;
+ } else if (what == "modulate") {
+ ctd->modulate = p_value;
+ } else if (what == "region") {
+ ctd->region = p_value;
+ } else if (what == "tile_mode") {
+ ctd->tile_mode = p_value;
+ } else if (what.left(9) == "autotile") {
+ what = what.right(9);
+ if (what == "bitmask_mode") {
+ ctd->autotile_bitmask_mode = p_value;
+ } else if (what == "icon_coordinate") {
+ ctd->autotile_icon_coordinate = p_value;
+ } else if (what == "tile_size") {
+ ctd->autotile_tile_size = p_value;
+ } else if (what == "spacing") {
+ ctd->autotile_spacing = p_value;
+ } else if (what == "bitmask_flags") {
+ if (p_value.is_array()) {
+ Array p = p_value;
+ Vector2i last_coord;
+ while (p.size() > 0) {
+ if (p[0].get_type() == Variant::VECTOR2) {
+ last_coord = p[0];
+ } else if (p[0].get_type() == Variant::INT) {
+ ctd->autotile_bitmask_flags.insert(last_coord, p[0]);
+ }
+ p.pop_front();
+ }
+ }
+ } else if (what == "occluder_map") {
+ Array p = p_value;
+ Vector2 last_coord;
+ while (p.size() > 0) {
+ if (p[0].get_type() == Variant::VECTOR2) {
+ last_coord = p[0];
+ } else if (p[0].get_type() == Variant::OBJECT) {
+ ctd->autotile_occluder_map.insert(last_coord, p[0]);
+ }
+ p.pop_front();
+ }
+ } else if (what == "navpoly_map") {
+ Array p = p_value;
+ Vector2 last_coord;
+ while (p.size() > 0) {
+ if (p[0].get_type() == Variant::VECTOR2) {
+ last_coord = p[0];
+ } else if (p[0].get_type() == Variant::OBJECT) {
+ ctd->autotile_navpoly_map.insert(last_coord, p[0]);
+ }
+ p.pop_front();
+ }
+ } else if (what == "priority_map") {
+ Array p = p_value;
+ Vector3 val;
+ Vector2 v;
+ int priority;
+ while (p.size() > 0) {
+ val = p[0];
+ if (val.z > 1) {
+ v.x = val.x;
+ v.y = val.y;
+ priority = (int)val.z;
+ ctd->autotile_priority_map.insert(v, priority);
+ }
+ p.pop_front();
+ }
+ } else if (what == "z_index_map") {
+ Array p = p_value;
+ Vector3 val;
+ Vector2 v;
+ int z_index;
+ while (p.size() > 0) {
+ val = p[0];
+ if (val.z != 0) {
+ v.x = val.x;
+ v.y = val.y;
+ z_index = (int)val.z;
+ ctd->autotile_z_index_map.insert(v, z_index);
+ }
+ p.pop_front();
+ }
+ }
+
+ } else if (what == "shapes") {
+ Array p = p_value;
+ for (int i = 0; i < p.size(); i++) {
+ CompatibilityShapeData csd;
+ Dictionary d = p[i];
+ for (int j = 0; j < d.size(); j++) {
+ String key = d.get_key_at_index(j);
+ if (key == "autotile_coord") {
+ csd.autotile_coords = d[key];
+ } else if (key == "one_way") {
+ csd.one_way = d[key];
+ } else if (key == "one_way_margin") {
+ csd.one_way_margin = d[key];
+ } else if (key == "shape") {
+ csd.shape = d[key];
+ } else if (key == "shape_transform") {
+ csd.transform = d[key];
+ }
+ }
+ ctd->shapes.push_back(csd);
+ }
+
+ /*
+ // IGNORED FOR NOW, they seem duplicated data compared to the shapes array
+ } else if (what == "shape") {
+ // TODO
+ } else if (what == "shape_offset") {
+ // TODO
+ } else if (what == "shape_transform") {
+ // TODO
+ } else if (what == "shape_one_way") {
+ // TODO
+ } else if (what == "shape_one_way_margin") {
+ // TODO
}
- }
+ // IGNORED FOR NOW, maybe useless ?
+ else if (what == "occluder_offset") {
+ // Not
+ } else if (what == "navigation_offset") {
+ // TODO
+ }
+ */
- if (coords.size() == 0) {
- return autotile_get_icon_coordinate(p_id);
+ } else if (what == "z_index") {
+ ctd->z_index = p_value;
+
+ // TODO: remove the conversion from here, it's not where it should be done
+ compatibility_conversion();
+ } else {
+ return false;
+ }
} else {
- uint32_t picked_value = Math::rand() % priority_sum;
- uint32_t upper_bound;
- uint32_t lower_bound = 0;
- Vector2 result = coords.front()->get();
- List<Vector2>::Element *coords_E = coords.front();
- List<uint32_t>::Element *priorities_E = priorities.front();
- while (priorities_E) {
- upper_bound = lower_bound + priorities_E->get();
- if (lower_bound <= picked_value && picked_value < upper_bound) {
- result = coords_E->get();
- break;
+#endif // DISABLE_DEPRECATED
+
+ // This is now a new property.
+ if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_integer()) {
+ // Occlusion layers.
+ int index = components[0].trim_prefix("occlusion_layer_").to_int();
+ ERR_FAIL_COND_V(index < 0, false);
+ if (components[1] == "light_mask") {
+ ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false);
+ if (index >= occlusion_layers.size()) {
+ set_occlusion_layers_count(index + 1);
+ }
+ set_occlusion_layer_light_mask(index, p_value);
+ return true;
+ } else if (components[1] == "sdf_collision") {
+ ERR_FAIL_COND_V(p_value.get_type() != Variant::BOOL, false);
+ if (index >= occlusion_layers.size()) {
+ set_occlusion_layers_count(index + 1);
+ }
+ set_occlusion_layer_sdf_collision(index, p_value);
+ return true;
+ }
+ } else if (components.size() == 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_integer()) {
+ // Physics layers.
+ int index = components[0].trim_prefix("physics_layer_").to_int();
+ ERR_FAIL_COND_V(index < 0, false);
+ if (components[1] == "collision_layer") {
+ ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false);
+ if (index >= physics_layers.size()) {
+ set_physics_layers_count(index + 1);
+ }
+ set_physics_layer_collision_layer(index, p_value);
+ return true;
+ } else if (components[1] == "collision_mask") {
+ ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false);
+ if (index >= physics_layers.size()) {
+ set_physics_layers_count(index + 1);
+ }
+ set_physics_layer_collision_mask(index, p_value);
+ return true;
+ } else if (components[1] == "physics_material") {
+ Ref<PhysicsMaterial> physics_material = p_value;
+ ERR_FAIL_COND_V(!physics_material.is_valid(), false);
+ if (index >= physics_layers.size()) {
+ set_physics_layers_count(index + 1);
+ }
+ set_physics_layer_physics_material(index, physics_material);
+ return true;
+ }
+ } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_integer()) {
+ // Terrains.
+ int terrain_set_index = components[0].trim_prefix("terrain_set_").to_int();
+ ERR_FAIL_COND_V(terrain_set_index < 0, false);
+ if (components[1] == "mode") {
+ ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false);
+ if (terrain_set_index >= terrain_sets.size()) {
+ set_terrain_sets_count(terrain_set_index + 1);
+ }
+ set_terrain_set_mode(terrain_set_index, TerrainMode(int(p_value)));
+ } else if (components[1] == "terrains_count") {
+ ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false);
+ if (terrain_set_index >= terrain_sets.size()) {
+ set_terrain_sets_count(terrain_set_index + 1);
+ }
+ set_terrains_count(terrain_set_index, p_value);
+ return true;
+ } else if (components.size() >= 3 && components[1].begins_with("terrain_") && components[1].trim_prefix("terrain_").is_valid_integer()) {
+ int terrain_index = components[1].trim_prefix("terrain_").to_int();
+ ERR_FAIL_COND_V(terrain_index < 0, false);
+ if (components[2] == "name") {
+ ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false);
+ if (terrain_set_index >= terrain_sets.size()) {
+ set_terrain_sets_count(terrain_set_index + 1);
+ }
+ if (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) {
+ set_terrains_count(terrain_set_index, terrain_index + 1);
+ }
+ set_terrain_name(terrain_set_index, terrain_index, p_value);
+ return true;
+ } else if (components[2] == "color") {
+ ERR_FAIL_COND_V(p_value.get_type() != Variant::COLOR, false);
+ if (terrain_set_index >= terrain_sets.size()) {
+ set_terrain_sets_count(terrain_set_index + 1);
+ }
+ if (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) {
+ set_terrains_count(terrain_set_index, terrain_index + 1);
+ }
+ set_terrain_color(terrain_set_index, terrain_index, p_value);
+ return true;
+ }
+ }
+ } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_integer()) {
+ // Navigation layers.
+ int index = components[0].trim_prefix("navigation_layer_").to_int();
+ ERR_FAIL_COND_V(index < 0, false);
+ if (components[1] == "layers") {
+ ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false);
+ if (index >= navigation_layers.size()) {
+ set_navigation_layers_count(index + 1);
+ }
+ set_navigation_layer_layers(index, p_value);
+ return true;
+ }
+ } else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_integer()) {
+ // Custom data layers.
+ int index = components[0].trim_prefix("custom_data_layer_").to_int();
+ ERR_FAIL_COND_V(index < 0, false);
+ if (components[1] == "name") {
+ ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false);
+ if (index >= custom_data_layers.size()) {
+ set_custom_data_layers_count(index + 1);
+ }
+ set_custom_data_name(index, p_value);
+ return true;
+ } else if (components[1] == "type") {
+ ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false);
+ if (index >= custom_data_layers.size()) {
+ set_custom_data_layers_count(index + 1);
+ }
+ set_custom_data_type(index, Variant::Type(int(p_value)));
+ return true;
}
- lower_bound = upper_bound;
- priorities_E = priorities_E->next();
- coords_E = coords_E->next();
+ } else if (components.size() == 2 && components[0] == "sources" && components[1].is_valid_integer()) {
+ // Create atlas if it does not exists.
+ int source_id = components[1].to_int();
+
+ if (!has_source(source_id)) {
+ add_source(p_value, source_id);
+ }
+ return true;
}
- return result;
+#ifndef DISABLE_DEPRECATED
}
+#endif // DISABLE_DEPRECATED
+
+ return false;
}
-Vector2 TileSet::atlastile_get_subtile_by_priority(int p_id, const Node *p_tilemap_node, const Vector2 &p_tile_location) {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Vector2());
- //First try to forward selection to script
- if (get_script_instance() != nullptr) {
- if (get_script_instance()->has_method("_forward_atlas_subtile_selection")) {
- Variant ret = get_script_instance()->call("_forward_atlas_subtile_selection", p_id, p_tilemap_node, p_tile_location);
- if (ret.get_type() == Variant::VECTOR2) {
- return ret;
+bool TileSet::_get(const StringName &p_name, Variant &r_ret) const {
+ Vector<String> components = String(p_name).split("/", true, 2);
+
+ if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_integer()) {
+ // Occlusion layers.
+ int index = components[0].trim_prefix("occlusion_layer_").to_int();
+ if (index < 0 || index >= occlusion_layers.size()) {
+ return false;
+ }
+ if (components[1] == "light_mask") {
+ r_ret = get_occlusion_layer_light_mask(index);
+ return true;
+ } else if (components[1] == "sdf_collision") {
+ r_ret = get_occlusion_layer_sdf_collision(index);
+ return true;
+ }
+ } else if (components.size() == 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_integer()) {
+ // Physics layers.
+ int index = components[0].trim_prefix("physics_layer_").to_int();
+ if (index < 0 || index >= physics_layers.size()) {
+ return false;
+ }
+ if (components[1] == "collision_layer") {
+ r_ret = get_physics_layer_collision_layer(index);
+ return true;
+ } else if (components[1] == "collision_mask") {
+ r_ret = get_physics_layer_collision_mask(index);
+ return true;
+ } else if (components[1] == "physics_material") {
+ r_ret = get_physics_layer_physics_material(index);
+ return true;
+ }
+ } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_integer()) {
+ // Terrains.
+ int terrain_set_index = components[0].trim_prefix("terrain_set_").to_int();
+ if (terrain_set_index < 0 || terrain_set_index >= terrain_sets.size()) {
+ return false;
+ }
+ if (components[1] == "mode") {
+ r_ret = get_terrain_set_mode(terrain_set_index);
+ return true;
+ } else if (components[1] == "terrains_count") {
+ r_ret = get_terrains_count(terrain_set_index);
+ return true;
+ } else if (components.size() >= 3 && components[1].begins_with("terrain_") && components[1].trim_prefix("terrain_").is_valid_integer()) {
+ int terrain_index = components[1].trim_prefix("terrain_").to_int();
+ if (terrain_index < 0 || terrain_index >= terrain_sets[terrain_set_index].terrains.size()) {
+ return false;
+ }
+ if (components[2] == "name") {
+ r_ret = get_terrain_name(terrain_set_index, terrain_index);
+ return true;
+ } else if (components[2] == "color") {
+ r_ret = get_terrain_color(terrain_set_index, terrain_index);
+ return true;
}
}
+ } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_integer()) {
+ // navigation layers.
+ int index = components[0].trim_prefix("navigation_layer_").to_int();
+ if (index < 0 || index >= navigation_layers.size()) {
+ return false;
+ }
+ if (components[1] == "layers") {
+ r_ret = get_navigation_layer_layers(index);
+ return true;
+ }
+ } else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_integer()) {
+ // Custom data layers.
+ int index = components[0].trim_prefix("custom_data_layer_").to_int();
+ if (index < 0 || index >= custom_data_layers.size()) {
+ return false;
+ }
+ if (components[1] == "name") {
+ r_ret = get_custom_data_name(index);
+ return true;
+ } else if (components[1] == "type") {
+ r_ret = get_custom_data_type(index);
+ return true;
+ }
+ } else if (components.size() == 2 && components[0] == "sources" && components[1].is_valid_integer()) {
+ // Atlases data.
+ int source_id = components[1].to_int();
+
+ if (has_source(source_id)) {
+ r_ret = get_source(source_id);
+ return true;
+ } else {
+ return false;
+ }
}
- Vector2 coord = tile_get_region(p_id).size / autotile_get_size(p_id);
+ return false;
+}
- List<Vector2> coords;
- for (int x = 0; x < coord.x; x++) {
- for (int y = 0; y < coord.y; y++) {
- for (int i = 0; i < autotile_get_subtile_priority(p_id, Vector2(x, y)); i++) {
- coords.push_back(Vector2(x, y));
- }
+void TileSet::_get_property_list(List<PropertyInfo> *p_list) const {
+ PropertyInfo property_info;
+ // Rendering.
+ p_list->push_back(PropertyInfo(Variant::NIL, "Rendering", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
+ for (int i = 0; i < occlusion_layers.size(); i++) {
+ p_list->push_back(PropertyInfo(Variant::INT, vformat("occlusion_layer_%d/light_mask", i), PROPERTY_HINT_LAYERS_2D_RENDER));
+
+ // occlusion_layer_%d/sdf_collision
+ property_info = PropertyInfo(Variant::BOOL, vformat("occlusion_layer_%d/sdf_collision", i));
+ if (occlusion_layers[i].sdf_collision == false) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
}
+ p_list->push_back(property_info);
}
- if (coords.size() == 0) {
- return autotile_get_icon_coordinate(p_id);
- } else {
- return coords[Math::random(0, (int)coords.size())];
+
+ // Physics.
+ p_list->push_back(PropertyInfo(Variant::NIL, "Physics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
+ for (int i = 0; i < physics_layers.size(); i++) {
+ p_list->push_back(PropertyInfo(Variant::INT, vformat("physics_layer_%d/collision_layer", i), PROPERTY_HINT_LAYERS_2D_PHYSICS));
+
+ // physics_layer_%d/collision_mask
+ property_info = PropertyInfo(Variant::INT, vformat("physics_layer_%d/collision_mask", i), PROPERTY_HINT_LAYERS_2D_PHYSICS);
+ if (physics_layers[i].collision_mask == 1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+
+ // physics_layer_%d/physics_material
+ property_info = PropertyInfo(Variant::OBJECT, vformat("physics_layer_%d/physics_material", i), PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial");
+ if (!physics_layers[i].physics_material.is_valid()) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
}
-}
-void TileSet::tile_set_name(int p_id, const String &p_name) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].name = p_name;
- emit_changed();
+ // Terrains.
+ p_list->push_back(PropertyInfo(Variant::NIL, "Terrains", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
+ for (int terrain_set_index = 0; terrain_set_index < terrain_sets.size(); terrain_set_index++) {
+ p_list->push_back(PropertyInfo(Variant::INT, vformat("terrain_set_%d/mode", terrain_set_index), PROPERTY_HINT_ENUM, "Match corners and sides,Match corners,Match sides"));
+ p_list->push_back(PropertyInfo(Variant::INT, vformat("terrain_set_%d/terrains_count", terrain_set_index), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR));
+ for (int terrain_index = 0; terrain_index < terrain_sets[terrain_set_index].terrains.size(); terrain_index++) {
+ p_list->push_back(PropertyInfo(Variant::STRING, vformat("terrain_set_%d/terrain_%d/name", terrain_set_index, terrain_index)));
+ p_list->push_back(PropertyInfo(Variant::COLOR, vformat("terrain_set_%d/terrain_%d/color", terrain_set_index, terrain_index)));
+ }
+ }
+
+ // Navigation.
+ p_list->push_back(PropertyInfo(Variant::NIL, "Navigation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
+ for (int i = 0; i < navigation_layers.size(); i++) {
+ p_list->push_back(PropertyInfo(Variant::INT, vformat("navigation_layer_%d/layers", i), PROPERTY_HINT_LAYERS_2D_NAVIGATION));
+ }
+
+ // Custom data.
+ String argt = "Any";
+ for (int i = 1; i < Variant::VARIANT_MAX; i++) {
+ argt += "," + Variant::get_type_name(Variant::Type(i));
+ }
+ p_list->push_back(PropertyInfo(Variant::NIL, "Custom data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
+ for (int i = 0; i < custom_data_layers.size(); i++) {
+ p_list->push_back(PropertyInfo(Variant::STRING, vformat("custom_data_layer_%d/name", i)));
+ p_list->push_back(PropertyInfo(Variant::INT, vformat("custom_data_layer_%d/type", i), PROPERTY_HINT_ENUM, argt));
+ }
+
+ // Sources.
+ // Note: sources have to be listed in at the end as some TileData rely on the TileSet properties being initialized first.
+ for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) {
+ p_list->push_back(PropertyInfo(Variant::INT, vformat("sources/%d", E_source->key()), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ }
}
-String TileSet::tile_get_name(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), String());
- return tile_map[p_id].name;
+void TileSet::_bind_methods() {
+ // Sources management.
+ ClassDB::bind_method(D_METHOD("get_next_source_id"), &TileSet::get_next_source_id);
+ ClassDB::bind_method(D_METHOD("add_source", "atlas_source_id_override"), &TileSet::add_source, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("remove_source", "source_id"), &TileSet::remove_source);
+ ClassDB::bind_method(D_METHOD("set_source_id", "source_id"), &TileSet::set_source_id);
+ ClassDB::bind_method(D_METHOD("get_source_count"), &TileSet::get_source_count);
+ ClassDB::bind_method(D_METHOD("get_source_id", "index"), &TileSet::get_source_id);
+ ClassDB::bind_method(D_METHOD("has_source", "index"), &TileSet::has_source);
+ ClassDB::bind_method(D_METHOD("get_source", "index"), &TileSet::get_source);
+
+ // Shape and layout.
+ ClassDB::bind_method(D_METHOD("set_tile_shape", "shape"), &TileSet::set_tile_shape);
+ ClassDB::bind_method(D_METHOD("get_tile_shape"), &TileSet::get_tile_shape);
+ ClassDB::bind_method(D_METHOD("set_tile_layout", "layout"), &TileSet::set_tile_layout);
+ ClassDB::bind_method(D_METHOD("get_tile_layout"), &TileSet::get_tile_layout);
+ ClassDB::bind_method(D_METHOD("set_tile_offset_axis", "alignment"), &TileSet::set_tile_offset_axis);
+ ClassDB::bind_method(D_METHOD("get_tile_offset_axis"), &TileSet::get_tile_offset_axis);
+ ClassDB::bind_method(D_METHOD("set_tile_size", "size"), &TileSet::set_tile_size);
+ ClassDB::bind_method(D_METHOD("get_tile_size"), &TileSet::get_tile_size);
+ ClassDB::bind_method(D_METHOD("set_tile_skew", "skew"), &TileSet::set_tile_skew);
+ ClassDB::bind_method(D_METHOD("get_tile_skew"), &TileSet::get_tile_skew);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_shape", PROPERTY_HINT_ENUM, "Square,Isometric,Half-offset square,Hexagon"), "set_tile_shape", "get_tile_shape");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_layout", PROPERTY_HINT_ENUM, "Stacked,Stacked Offset,Stairs Right,Stairs Down,Diamond Right,Diamond Down"), "set_tile_layout", "get_tile_layout");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_offset_axis", PROPERTY_HINT_ENUM, "Horizontal Offset,Vertical Offset"), "set_tile_offset_axis", "get_tile_offset_axis");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "tile_size"), "set_tile_size", "get_tile_size");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "tile_skew"), "set_tile_skew", "get_tile_skew");
+
+ // Rendering.
+ ClassDB::bind_method(D_METHOD("set_uv_clipping", "uv_clipping"), &TileSet::set_uv_clipping);
+ ClassDB::bind_method(D_METHOD("is_uv_clipping"), &TileSet::is_uv_clipping);
+ ClassDB::bind_method(D_METHOD("set_y_sorting", "y_sorting"), &TileSet::set_y_sorting);
+ ClassDB::bind_method(D_METHOD("is_y_sorting"), &TileSet::is_y_sorting);
+
+ ClassDB::bind_method(D_METHOD("set_occlusion_layers_count", "occlusion_layers_count"), &TileSet::set_occlusion_layers_count);
+ ClassDB::bind_method(D_METHOD("get_occlusion_layers_count"), &TileSet::get_occlusion_layers_count);
+ ClassDB::bind_method(D_METHOD("set_occlusion_layer_light_mask", "layer_index", "light_mask"), &TileSet::set_occlusion_layer_light_mask);
+ ClassDB::bind_method(D_METHOD("get_occlusion_layer_light_mask"), &TileSet::get_occlusion_layer_light_mask);
+ ClassDB::bind_method(D_METHOD("set_occlusion_layer_sdf_collision", "layer_index", "sdf_collision"), &TileSet::set_occlusion_layer_sdf_collision);
+ ClassDB::bind_method(D_METHOD("get_occlusion_layer_sdf_collision"), &TileSet::get_occlusion_layer_sdf_collision);
+
+ // Physics
+ ClassDB::bind_method(D_METHOD("set_physics_layers_count", "physics_layers_count"), &TileSet::set_physics_layers_count);
+ ClassDB::bind_method(D_METHOD("get_physics_layers_count"), &TileSet::get_physics_layers_count);
+ ClassDB::bind_method(D_METHOD("set_physics_layer_collision_layer", "layer_index", "layer"), &TileSet::set_physics_layer_collision_layer);
+ ClassDB::bind_method(D_METHOD("get_physics_layer_collision_layer", "layer_index"), &TileSet::get_physics_layer_collision_layer);
+ ClassDB::bind_method(D_METHOD("set_physics_layer_collision_mask", "layer_index", "mask"), &TileSet::set_physics_layer_collision_mask);
+ ClassDB::bind_method(D_METHOD("get_physics_layer_collision_mask", "layer_index"), &TileSet::get_physics_layer_collision_mask);
+ ClassDB::bind_method(D_METHOD("set_physics_layer_physics_material", "layer_index", "physics_material"), &TileSet::set_physics_layer_physics_material);
+ ClassDB::bind_method(D_METHOD("get_physics_layer_physics_material", "layer_index"), &TileSet::get_physics_layer_physics_material);
+
+ // Terrains
+ ClassDB::bind_method(D_METHOD("set_terrain_sets_count", "terrain_sets_count"), &TileSet::set_terrain_sets_count);
+ ClassDB::bind_method(D_METHOD("get_terrain_sets_count"), &TileSet::get_terrain_sets_count);
+ ClassDB::bind_method(D_METHOD("set_terrain_set_mode", "terrain_set", "mode"), &TileSet::set_terrain_set_mode);
+ ClassDB::bind_method(D_METHOD("get_terrain_set_mode", "terrain_set"), &TileSet::get_terrain_set_mode);
+
+ ClassDB::bind_method(D_METHOD("set_terrains_count", "terrain_set", "terrains_count"), &TileSet::set_terrains_count);
+ ClassDB::bind_method(D_METHOD("get_terrains_count", "terrain_set"), &TileSet::get_terrains_count);
+ ClassDB::bind_method(D_METHOD("set_terrain_name", "terrain_set", "terrain_index", "name"), &TileSet::set_terrain_name);
+ ClassDB::bind_method(D_METHOD("get_terrain_name", "terrain_set", "terrain_index"), &TileSet::get_terrain_name);
+ ClassDB::bind_method(D_METHOD("set_terrain_color", "terrain_set", "terrain_index", "color"), &TileSet::set_terrain_color);
+ ClassDB::bind_method(D_METHOD("get_terrain_color", "terrain_set", "terrain_index"), &TileSet::get_terrain_color);
+
+ // Navigation
+ ClassDB::bind_method(D_METHOD("set_navigation_layers_count", "navigation_layers_count"), &TileSet::set_navigation_layers_count);
+ ClassDB::bind_method(D_METHOD("get_navigation_layers_count"), &TileSet::get_navigation_layers_count);
+ ClassDB::bind_method(D_METHOD("set_navigation_layer_layers", "layer_index", "layers"), &TileSet::set_navigation_layer_layers);
+ ClassDB::bind_method(D_METHOD("get_navigation_layer_layers", "layer_index"), &TileSet::get_navigation_layer_layers);
+
+ // Custom data
+ ClassDB::bind_method(D_METHOD("set_custom_data_layers_count", "custom_data_layers_count"), &TileSet::set_custom_data_layers_count);
+ ClassDB::bind_method(D_METHOD("get_custom_data_layers_count"), &TileSet::get_custom_data_layers_count);
+
+ ADD_GROUP("Rendering", "");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uv_clipping"), "set_uv_clipping", "is_uv_clipping");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "y_sorting"), "set_y_sorting", "is_y_sorting");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "occlusion_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_occlusion_layers_count", "get_occlusion_layers_count");
+
+ ADD_GROUP("Physics", "");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_physics_layers_count", "get_physics_layers_count");
+
+ ADD_GROUP("Terrains", "");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "terrains_sets_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_terrain_sets_count", "get_terrain_sets_count");
+
+ ADD_GROUP("Navigation", "");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_navigation_layers_count", "get_navigation_layers_count");
+
+ ADD_GROUP("Custom data", "");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "custom_data_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_custom_data_layers_count", "get_custom_data_layers_count");
+
+ // -- Enum binding --
+ BIND_ENUM_CONSTANT(TILE_SHAPE_SQUARE);
+ BIND_ENUM_CONSTANT(TILE_SHAPE_ISOMETRIC);
+ BIND_ENUM_CONSTANT(TILE_SHAPE_HALF_OFFSET_SQUARE);
+ BIND_ENUM_CONSTANT(TILE_SHAPE_HEXAGON);
+
+ BIND_ENUM_CONSTANT(TILE_LAYOUT_STACKED);
+ BIND_ENUM_CONSTANT(TILE_LAYOUT_STACKED_OFFSET);
+ BIND_ENUM_CONSTANT(TILE_LAYOUT_STAIRS_RIGHT);
+ BIND_ENUM_CONSTANT(TILE_LAYOUT_STAIRS_DOWN);
+ BIND_ENUM_CONSTANT(TILE_LAYOUT_DIAMOND_RIGHT);
+ BIND_ENUM_CONSTANT(TILE_LAYOUT_DIAMOND_DOWN);
+
+ BIND_ENUM_CONSTANT(TILE_OFFSET_AXIS_HORIZONTAL);
+ BIND_ENUM_CONSTANT(TILE_OFFSET_AXIS_VERTICAL);
+
+ BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_RIGHT_SIDE);
+ BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_RIGHT_CORNER);
+ BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE);
+ BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER);
+ BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE);
+ BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_BOTTOM_CORNER);
+ BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE);
+ BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER);
+ BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_LEFT_CORNER);
+ BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER);
+ BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_TOP_CORNER);
+ BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER);
+
+ BIND_ENUM_CONSTANT(TERRAIN_MODE_MATCH_CORNERS_AND_SIDES);
+ BIND_ENUM_CONSTANT(TERRAIN_MODE_MATCH_CORNERS);
+ BIND_ENUM_CONSTANT(TERRAIN_MODE_MATCH_SIDES);
}
-void TileSet::tile_clear_shapes(int p_id) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].shapes_data.clear();
+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));
}
-void TileSet::tile_add_shape(int p_id, const Ref<Shape2D> &p_shape, const Transform2D &p_transform, bool p_one_way, const Vector2 &p_autotile_coord) {
- ERR_FAIL_COND(!tile_map.has(p_id));
+TileSet::~TileSet() {
+ for (Map<int, CompatibilityTileData *>::Element *E = compatibility_data.front(); E; E = E->next()) {
+ memdelete(E->get());
+ }
+ while (!source_ids.is_empty()) {
+ remove_source(source_ids[0]);
+ }
+ for (int i = 0; i < tile_set_plugins_vector.size(); i++) {
+ memdelete(tile_set_plugins_vector[i]);
+ }
+}
- ShapeData new_data = ShapeData();
- new_data.shape = p_shape;
- new_data.shape_transform = p_transform;
- new_data.one_way_collision = p_one_way;
- new_data.autotile_coord = p_autotile_coord;
+/////////////////////////////// TileSetSource //////////////////////////////////////
- tile_map[p_id].shapes_data.push_back(new_data);
+void TileSetSource::set_tile_set(const TileSet *p_tile_set) {
+ tile_set = p_tile_set;
}
-int TileSet::tile_get_shape_count(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), 0);
- return tile_map[p_id].shapes_data.size();
+/////////////////////////////// TileSetAtlasSource //////////////////////////////////////
+
+void TileSetAtlasSource::set_tile_set(const TileSet *p_tile_set) {
+ tile_set = p_tile_set;
+
+ // Set the TileSet on all TileData.
+ for (Map<Vector2i, TileAlternativesData>::Element *E_tile = tiles.front(); E_tile; E_tile = E_tile->next()) {
+ for (Map<int, TileData *>::Element *E_alternative = E_tile->get().alternatives.front(); E_alternative; E_alternative = E_alternative->next()) {
+ E_alternative->get()->set_tile_set(tile_set);
+ }
+ }
}
-void TileSet::tile_set_shape(int p_id, int p_shape_id, const Ref<Shape2D> &p_shape) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- ERR_FAIL_COND(p_shape_id < 0);
+void TileSetAtlasSource::notify_tile_data_properties_should_change() {
+ // Set the TileSet on all TileData.
+ for (Map<Vector2i, TileAlternativesData>::Element *E_tile = tiles.front(); E_tile; E_tile = E_tile->next()) {
+ for (Map<int, TileData *>::Element *E_alternative = E_tile->get().alternatives.front(); E_alternative; E_alternative = E_alternative->next()) {
+ E_alternative->get()->notify_tile_data_properties_should_change();
+ }
+ }
+}
- if (p_shape_id >= tile_map[p_id].shapes_data.size()) {
- tile_map[p_id].shapes_data.resize(p_shape_id + 1);
+void TileSetAtlasSource::reset_state() {
+ // Reset all TileData.
+ for (Map<Vector2i, TileAlternativesData>::Element *E_tile = tiles.front(); E_tile; E_tile = E_tile->next()) {
+ for (Map<int, TileData *>::Element *E_alternative = E_tile->get().alternatives.front(); E_alternative; E_alternative = E_alternative->next()) {
+ E_alternative->get()->reset_state();
+ }
}
- tile_map[p_id].shapes_data.write[p_shape_id].shape = p_shape;
- _decompose_convex_shape(p_shape);
+}
+
+void TileSetAtlasSource::set_texture(Ref<Texture2D> p_texture) {
+ texture = p_texture;
+
emit_changed();
}
-Ref<Shape2D> TileSet::tile_get_shape(int p_id, int p_shape_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Ref<Shape2D>());
- ERR_FAIL_COND_V(p_shape_id < 0, Ref<Shape2D>());
+Ref<Texture2D> TileSetAtlasSource::get_texture() const {
+ return texture;
+}
- if (p_shape_id < tile_map[p_id].shapes_data.size()) {
- return tile_map[p_id].shapes_data[p_shape_id].shape;
+void TileSetAtlasSource::set_margins(Vector2i p_margins) {
+ if (p_margins.x < 0 || p_margins.y < 0) {
+ WARN_PRINT("Atlas source margins should be positive.");
+ margins = Vector2i(MAX(0, p_margins.x), MAX(0, p_margins.y));
+ } else {
+ margins = p_margins;
}
- return Ref<Shape2D>();
+ emit_changed();
+}
+Vector2i TileSetAtlasSource::get_margins() const {
+ return margins;
}
-void TileSet::tile_set_shape_transform(int p_id, int p_shape_id, const Transform2D &p_offset) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- ERR_FAIL_COND(p_shape_id < 0);
+void TileSetAtlasSource::set_separation(Vector2i p_separation) {
+ if (p_separation.x < 0 || p_separation.y < 0) {
+ WARN_PRINT("Atlas source separation should be positive.");
+ separation = Vector2i(MAX(0, p_separation.x), MAX(0, p_separation.y));
+ } else {
+ separation = p_separation;
+ }
+
+ emit_changed();
+}
+Vector2i TileSetAtlasSource::get_separation() const {
+ return separation;
+}
- if (p_shape_id >= tile_map[p_id].shapes_data.size()) {
- tile_map[p_id].shapes_data.resize(p_shape_id + 1);
+void TileSetAtlasSource::set_texture_region_size(Vector2i p_tile_size) {
+ if (p_tile_size.x <= 0 || p_tile_size.y <= 0) {
+ WARN_PRINT("Atlas source tile_size should be strictly positive.");
+ texture_region_size = Vector2i(MAX(1, p_tile_size.x), MAX(1, p_tile_size.y));
+ } else {
+ texture_region_size = p_tile_size;
}
- tile_map[p_id].shapes_data.write[p_shape_id].shape_transform = p_offset;
+
emit_changed();
}
+Vector2i TileSetAtlasSource::get_texture_region_size() const {
+ return texture_region_size;
+}
+
+Vector2i TileSetAtlasSource::get_atlas_grid_size() const {
+ Ref<Texture2D> texture = get_texture();
+ if (!texture.is_valid()) {
+ return Vector2i();
+ }
+
+ ERR_FAIL_COND_V(texture_region_size.x <= 0 || texture_region_size.y <= 0, Vector2i());
+
+ Size2i valid_area = texture->get_size() - margins;
+
+ // Compute the number of valid tiles in the tiles atlas
+ Size2i grid_size = Size2i();
+ if (valid_area.x >= texture_region_size.x && valid_area.y >= texture_region_size.y) {
+ valid_area -= texture_region_size;
+ grid_size = Size2i(1, 1) + valid_area / (texture_region_size + separation);
+ }
+ return grid_size;
+}
-Transform2D TileSet::tile_get_shape_transform(int p_id, int p_shape_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Transform2D());
- ERR_FAIL_COND_V(p_shape_id < 0, Transform2D());
+bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value) {
+ Vector<String> components = String(p_name).split("/", true, 2);
- if (p_shape_id < tile_map[p_id].shapes_data.size()) {
- return tile_map[p_id].shapes_data[p_shape_id].shape_transform;
+ // Compute the vector2i if we have coordinates.
+ Vector<String> coords_split = components[0].split(":");
+ Vector2i coords = TileSetAtlasSource::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());
}
- return Transform2D();
+ // Properties.
+ if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+ // Create the tile if needed.
+ if (!has_tile(coords)) {
+ create_tile(coords);
+ }
+ if (components.size() >= 2) {
+ // Properties.
+ if (components[1] == "size_in_atlas") {
+ move_tile_in_atlas(coords, coords, p_value);
+ } else if (components[1] == "next_alternative_id") {
+ 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) {
+ // Create the alternative if needed ?
+ if (!has_alternative_tile(coords, alternative_id)) {
+ create_alternative_tile(coords, alternative_id);
+ }
+ if (!tiles[coords].alternatives.has(alternative_id)) {
+ tiles[coords].alternatives[alternative_id] = memnew(TileData);
+ tiles[coords].alternatives[alternative_id]->set_tile_set(tile_set);
+ tiles[coords].alternatives[alternative_id]->set_allow_transform(alternative_id > 0);
+ tiles[coords].alternatives_ids.append(alternative_id);
+ }
+ if (components.size() >= 3) {
+ bool valid;
+ tiles[coords].alternatives[alternative_id]->set(components[2], p_value, &valid);
+ return valid;
+ } else {
+ // Only create the alternative if it did not exist yet.
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
}
-void TileSet::tile_set_shape_offset(int p_id, int p_shape_id, const Vector2 &p_offset) {
- Transform2D transform = tile_get_shape_transform(p_id, p_shape_id);
- transform.set_origin(p_offset);
- tile_set_shape_transform(p_id, p_shape_id, transform);
+bool TileSetAtlasSource::_get(const StringName &p_name, Variant &r_ret) const {
+ Vector<String> components = String(p_name).split("/", true, 2);
+
+ // Properties.
+ Vector<String> coords_split = components[0].split(":");
+ if (coords_split.size() == 2 && coords_split[0].is_valid_integer() && coords_split[1].is_valid_integer()) {
+ Vector2i coords = Vector2i(coords_split[0].to_int(), coords_split[1].to_int());
+ if (tiles.has(coords)) {
+ if (components.size() >= 2) {
+ // Properties.
+ if (components[1] == "size_in_atlas") {
+ r_ret = tiles[coords].size_in_atlas;
+ return true;
+ } else if (components[1] == "next_alternative_id") {
+ r_ret = tiles[coords].next_alternative_id;
+ return true;
+ } else if (components[1].is_valid_integer()) {
+ int alternative_id = components[1].to_int();
+ if (alternative_id != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE && tiles[coords].alternatives.has(alternative_id)) {
+ if (components.size() >= 3) {
+ bool valid;
+ r_ret = tiles[coords].alternatives[alternative_id]->get(components[2], &valid);
+ return valid;
+ } else {
+ // Only to notify the tile alternative exists.
+ r_ret = alternative_id;
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return false;
}
-Vector2 TileSet::tile_get_shape_offset(int p_id, int p_shape_id) const {
- return tile_get_shape_transform(p_id, p_shape_id).get_origin();
+void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const {
+ // Atlases data.
+ PropertyInfo property_info;
+ for (Map<Vector2i, TileAlternativesData>::Element *E_tile = tiles.front(); E_tile; E_tile = E_tile->next()) {
+ List<PropertyInfo> tile_property_list;
+
+ // size_in_atlas
+ property_info = PropertyInfo(Variant::VECTOR2I, "size_in_atlas", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR);
+ if (E_tile->get().size_in_atlas == Vector2i(1, 1)) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ tile_property_list.push_back(property_info);
+
+ // next_alternative_id
+ property_info = PropertyInfo(Variant::INT, "next_alternative_id", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR);
+ if (E_tile->get().next_alternative_id == 1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ tile_property_list.push_back(property_info);
+
+ for (Map<int, TileData *>::Element *E_alternative = E_tile->get().alternatives.front(); E_alternative; E_alternative = E_alternative->next()) {
+ // Add a dummy property to show the alternative exists.
+ tile_property_list.push_back(PropertyInfo(Variant::INT, vformat("%d", E_alternative->key()), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+
+ // Get the alternative tile's properties and append them to the list of properties.
+ List<PropertyInfo> alternative_property_list;
+ E_alternative->get()->get_property_list(&alternative_property_list);
+ for (List<PropertyInfo>::Element *E_property = alternative_property_list.front(); E_property; E_property = E_property->next()) {
+ property_info = E_property->get();
+ bool valid;
+ Variant default_value = ClassDB::class_get_default_property_value("TileData", property_info.name, &valid);
+ Variant value = E_alternative->get()->get(property_info.name);
+ if (valid && value == default_value) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ property_info.name = vformat("%s/%s", vformat("%d", E_alternative->key()), property_info.name);
+ tile_property_list.push_back(property_info);
+ }
+ }
+
+ // Add all alternative.
+ for (List<PropertyInfo>::Element *E_property = tile_property_list.front(); E_property; E_property = E_property->next()) {
+ E_property->get().name = vformat("%s/%s", vformat("%d:%d", E_tile->key().x, E_tile->key().y), E_property->get().name);
+ p_list->push_back(E_property->get());
+ }
+ }
}
-void TileSet::tile_set_shape_one_way(int p_id, int p_shape_id, const bool p_one_way) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- ERR_FAIL_COND(p_shape_id < 0);
+void TileSetAtlasSource::create_tile(const Vector2i p_atlas_coords, const Vector2i p_size) {
+ // Create a tile if it does not exists.
+ ERR_FAIL_COND(p_atlas_coords.x < 0 || p_atlas_coords.y < 0);
+ ERR_FAIL_COND(p_size.x <= 0 || p_size.y <= 0);
+ for (int x = 0; x < p_size.x; x++) {
+ for (int y = 0; y < p_size.y; y++) {
+ Vector2i coords = p_atlas_coords + Vector2i(x, y);
+ ERR_FAIL_COND_MSG(tiles.has(coords), vformat("Cannot create tile at position %s with size %s. Already a tile present at %s.", p_atlas_coords, p_size, coords));
+ }
+ }
- if (p_shape_id >= tile_map[p_id].shapes_data.size()) {
- tile_map[p_id].shapes_data.resize(p_shape_id + 1);
+ // Create and resize the tile.
+ tiles.insert(p_atlas_coords, TileSetAtlasSource::TileAlternativesData());
+ tiles_ids.append(p_atlas_coords);
+ tiles_ids.sort();
+
+ tiles[p_atlas_coords].size_in_atlas = p_size;
+ tiles[p_atlas_coords].alternatives[0] = memnew(TileData);
+ tiles[p_atlas_coords].alternatives[0]->set_tile_set(tile_set);
+ tiles[p_atlas_coords].alternatives[0]->set_allow_transform(false);
+ tiles[p_atlas_coords].alternatives[0]->connect("changed", callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed));
+ tiles[p_atlas_coords].alternatives[0]->notify_property_list_changed();
+ tiles[p_atlas_coords].alternatives_ids.append(0);
+
+ // Add all covered positions to the mapping cache
+ for (int x = 0; x < p_size.x; x++) {
+ for (int y = 0; y < p_size.y; y++) {
+ Vector2i coords = p_atlas_coords + Vector2i(x, y);
+ _coords_mapping_cache[coords] = p_atlas_coords;
+ }
}
- tile_map[p_id].shapes_data.write[p_shape_id].one_way_collision = p_one_way;
- emit_changed();
+
+ emit_signal("changed");
}
-bool TileSet::tile_get_shape_one_way(int p_id, int p_shape_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), false);
- ERR_FAIL_COND_V(p_shape_id < 0, false);
+void TileSetAtlasSource::remove_tile(Vector2i p_atlas_coords) {
+ ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
+
+ // Remove all covered positions from the mapping cache
+ Size2i size = tiles[p_atlas_coords].size_in_atlas;
- if (p_shape_id < tile_map[p_id].shapes_data.size()) {
- return tile_map[p_id].shapes_data[p_shape_id].one_way_collision;
+ for (int x = 0; x < size.x; x++) {
+ for (int y = 0; y < size.y; y++) {
+ Vector2i coords = p_atlas_coords + Vector2i(x, y);
+ _coords_mapping_cache.erase(coords);
+ }
}
- return false;
+ // Free tile data.
+ for (Map<int, TileData *>::Element *E_tile_data = tiles[p_atlas_coords].alternatives.front(); E_tile_data; E_tile_data = E_tile_data->next()) {
+ memdelete(E_tile_data->get());
+ }
+
+ // Delete the tile
+ tiles.erase(p_atlas_coords);
+ tiles_ids.erase(p_atlas_coords);
+ tiles_ids.sort();
+
+ emit_signal("changed");
}
-void TileSet::tile_set_shape_one_way_margin(int p_id, int p_shape_id, float p_margin) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- ERR_FAIL_COND(p_shape_id < 0);
+bool TileSetAtlasSource::has_tile(Vector2i p_atlas_coords) const {
+ return tiles.has(p_atlas_coords);
+}
- if (p_shape_id >= tile_map[p_id].shapes_data.size()) {
- tile_map[p_id].shapes_data.resize(p_shape_id + 1);
+Vector2i TileSetAtlasSource::get_tile_at_coords(Vector2i p_atlas_coords) const {
+ if (!_coords_mapping_cache.has(p_atlas_coords)) {
+ return INVALID_ATLAS_COORDS;
}
- tile_map[p_id].shapes_data.write[p_shape_id].one_way_collision_margin = p_margin;
- emit_changed();
+
+ return _coords_mapping_cache[p_atlas_coords];
+}
+
+Vector2i TileSetAtlasSource::get_tile_size_in_atlas(Vector2i p_atlas_coords) const {
+ ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Vector2i(-1, -1), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
+
+ return tiles[p_atlas_coords].size_in_atlas;
}
-float TileSet::tile_get_shape_one_way_margin(int p_id, int p_shape_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), 0);
- ERR_FAIL_COND_V(p_shape_id < 0, 0);
+int TileSetAtlasSource::get_tiles_count() const {
+ return tiles_ids.size();
+}
+
+Vector2i TileSetAtlasSource::get_tile_id(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, tiles_ids.size(), TileSetAtlasSource::INVALID_ATLAS_COORDS);
+ return tiles_ids[p_index];
+}
+
+Rect2i TileSetAtlasSource::get_tile_texture_region(Vector2i p_atlas_coords) const {
+ ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Rect2i(), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
+
+ Vector2i size_in_atlas = tiles[p_atlas_coords].size_in_atlas;
+ Vector2 region_size = texture_region_size * size_in_atlas + separation * (size_in_atlas - Vector2i(1, 1));
+
+ Vector2 origin = margins + (p_atlas_coords * (texture_region_size + separation));
+
+ return Rect2(origin, region_size);
+ ;
+}
- if (p_shape_id < tile_map[p_id].shapes_data.size()) {
- return tile_map[p_id].shapes_data[p_shape_id].one_way_collision_margin;
+Vector2i TileSetAtlasSource::get_tile_effective_texture_offset(Vector2i p_atlas_coords, int p_alternative_tile) const {
+ ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Vector2i(), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords)));
+ ERR_FAIL_COND_V_MSG(!has_alternative_tile(p_atlas_coords, p_alternative_tile), Vector2i(), vformat("TileSetAtlasSource has no alternative tile with id %d at %s.", p_alternative_tile, String(p_atlas_coords)));
+ ERR_FAIL_COND_V(!tile_set, Vector2i());
+
+ Vector2 margin = (get_tile_texture_region(p_atlas_coords).size - tile_set->get_tile_size()) / 2;
+ margin = Vector2i(MAX(0, margin.x), MAX(0, margin.y));
+ Vector2i effective_texture_offset = Object::cast_to<TileData>(get_tile_data(p_atlas_coords, p_alternative_tile))->get_texture_offset();
+ if (ABS(effective_texture_offset.x) > margin.x || ABS(effective_texture_offset.y) > margin.y) {
+ effective_texture_offset.x = CLAMP(effective_texture_offset.x, -margin.x, margin.x);
+ effective_texture_offset.y = CLAMP(effective_texture_offset.y, -margin.y, margin.y);
}
- return 0;
+ return effective_texture_offset;
}
-void TileSet::tile_set_light_occluder(int p_id, const Ref<OccluderPolygon2D> &p_light_occluder) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].occluder = p_light_occluder;
+bool TileSetAtlasSource::can_move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords, Vector2i p_new_size) const {
+ ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), false, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
+
+ Vector2i new_atlas_coords = (p_new_atlas_coords != INVALID_ATLAS_COORDS) ? p_new_atlas_coords : p_atlas_coords;
+ if (new_atlas_coords.x < 0 || new_atlas_coords.y < 0) {
+ return false;
+ }
+
+ Vector2i size = (p_new_size != Vector2i(-1, -1)) ? p_new_size : tiles[p_atlas_coords].size_in_atlas;
+ ERR_FAIL_COND_V(size.x <= 0 || size.y <= 0, false);
+
+ Size2i grid_size = get_atlas_grid_size();
+ if (new_atlas_coords.x + size.x > grid_size.x || new_atlas_coords.y + size.y > grid_size.y) {
+ return false;
+ }
+
+ Rect2i new_rect = Rect2i(new_atlas_coords, size);
+ // Check if the new tile can fit in the new rect.
+ 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) {
+ return false;
+ }
+ }
+ }
+
+ return true;
}
-Ref<OccluderPolygon2D> TileSet::tile_get_light_occluder(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Ref<OccluderPolygon2D>());
- return tile_map[p_id].occluder;
+void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords, Vector2i p_new_size) {
+ bool can_move = can_move_tile_in_atlas(p_atlas_coords, p_new_atlas_coords, p_new_size);
+ ERR_FAIL_COND_MSG(!can_move, vformat("Cannot move tile at position %s with size %s. Tile already present.", p_new_atlas_coords, p_new_size));
+
+ // Compute the actual new rect from arguments.
+ Vector2i new_atlas_coords = (p_new_atlas_coords != INVALID_ATLAS_COORDS) ? p_new_atlas_coords : p_atlas_coords;
+ Vector2i size = (p_new_size != Vector2i(-1, -1)) ? p_new_size : tiles[p_atlas_coords].size_in_atlas;
+
+ if (new_atlas_coords == p_atlas_coords && size == tiles[p_atlas_coords].size_in_atlas) {
+ return;
+ }
+
+ // Remove all covered positions from the mapping cache.
+ Size2i old_size = tiles[p_atlas_coords].size_in_atlas;
+ for (int x = 0; x < old_size.x; x++) {
+ for (int y = 0; y < old_size.y; y++) {
+ Vector2i coords = p_atlas_coords + Vector2i(x, y);
+ _coords_mapping_cache.erase(coords);
+ }
+ }
+
+ // Move the tile and update its size.
+ if (new_atlas_coords != p_atlas_coords) {
+ tiles[new_atlas_coords] = tiles[p_atlas_coords];
+ tiles.erase(p_atlas_coords);
+
+ tiles_ids.erase(p_atlas_coords);
+ tiles_ids.append(new_atlas_coords);
+ tiles_ids.sort();
+ }
+ tiles[new_atlas_coords].size_in_atlas = size;
+
+ // Add all covered positions to the mapping cache again.
+ for (int x = 0; x < size.x; x++) {
+ for (int y = 0; y < size.y; y++) {
+ Vector2i coords = new_atlas_coords + Vector2i(x, y);
+ _coords_mapping_cache[coords] = new_atlas_coords;
+ }
+ }
+
+ emit_signal("changed");
}
-void TileSet::autotile_set_light_occluder(int p_id, const Ref<OccluderPolygon2D> &p_light_occluder, const Vector2 &p_coord) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- if (p_light_occluder.is_null()) {
- if (tile_map[p_id].autotile_data.occluder_map.has(p_coord)) {
- tile_map[p_id].autotile_data.occluder_map.erase(p_coord);
+bool TileSetAtlasSource::has_tiles_outside_texture() {
+ Vector2i grid_size = get_atlas_grid_size();
+ Vector<Vector2i> to_remove;
+
+ for (Map<Vector2i, TileSetAtlasSource::TileAlternativesData>::Element *E = tiles.front(); E; E = E->next()) {
+ if (E->key().x >= grid_size.x || E->key().y >= grid_size.y) {
+ return true;
}
- } else {
- tile_map[p_id].autotile_data.occluder_map[p_coord] = p_light_occluder;
}
+
+ return false;
}
-Ref<OccluderPolygon2D> TileSet::autotile_get_light_occluder(int p_id, const Vector2 &p_coord) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Ref<OccluderPolygon2D>());
+void TileSetAtlasSource::clear_tiles_outside_texture() {
+ Vector2i grid_size = get_atlas_grid_size();
+ Vector<Vector2i> to_remove;
- if (!tile_map[p_id].autotile_data.occluder_map.has(p_coord)) {
- return Ref<OccluderPolygon2D>();
- } else {
- return tile_map[p_id].autotile_data.occluder_map[p_coord];
+ for (Map<Vector2i, TileSetAtlasSource::TileAlternativesData>::Element *E = tiles.front(); E; E = E->next()) {
+ if (E->key().x >= grid_size.x || E->key().y >= grid_size.y) {
+ to_remove.append(E->key());
+ }
+ }
+
+ for (int i = 0; i < to_remove.size(); i++) {
+ remove_tile(to_remove[i]);
}
}
-void TileSet::tile_set_navigation_polygon_offset(int p_id, const Vector2 &p_offset) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].navigation_polygon_offset = p_offset;
+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));
+
+ int new_alternative_id = p_alternative_id_override >= 0 ? p_alternative_id_override : tiles[p_atlas_coords].next_alternative_id;
+
+ tiles[p_atlas_coords].alternatives[new_alternative_id] = memnew(TileData);
+ tiles[p_atlas_coords].alternatives[new_alternative_id]->set_tile_set(tile_set);
+ tiles[p_atlas_coords].alternatives[new_alternative_id]->set_allow_transform(true);
+ tiles[p_atlas_coords].alternatives[new_alternative_id]->notify_property_list_changed();
+ tiles[p_atlas_coords].alternatives_ids.append(new_alternative_id);
+ tiles[p_atlas_coords].alternatives_ids.sort();
+ _compute_next_alternative_id(p_atlas_coords);
+
+ emit_signal("changed");
+
+ return new_alternative_id;
+}
+
+void TileSetAtlasSource::remove_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) {
+ ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
+ ERR_FAIL_COND_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords)));
+ ERR_FAIL_COND_MSG(p_alternative_tile == 0, "Cannot remove the alternative with id 0, the base tile alternative cannot be removed.");
+
+ memdelete(tiles[p_atlas_coords].alternatives[p_alternative_tile]);
+ tiles[p_atlas_coords].alternatives.erase(p_alternative_tile);
+ tiles[p_atlas_coords].alternatives_ids.erase(p_alternative_tile);
+ tiles[p_atlas_coords].alternatives_ids.sort();
+
+ emit_signal("changed");
+}
+
+void TileSetAtlasSource::set_alternative_tile_id(const Vector2i p_atlas_coords, int p_alternative_tile, int p_new_id) {
+ ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
+ ERR_FAIL_COND_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords)));
+ ERR_FAIL_COND_MSG(p_alternative_tile == 0, "Cannot change the alternative with id 0, the base tile alternative cannot be modified.");
+
+ ERR_FAIL_COND_MSG(tiles[p_atlas_coords].alternatives.has(p_new_id), vformat("TileSetAtlasSource has already an alternative with id %d at %s.", p_new_id, String(p_atlas_coords)));
+
+ tiles[p_atlas_coords].alternatives[p_new_id] = tiles[p_atlas_coords].alternatives[p_alternative_tile];
+ tiles[p_atlas_coords].alternatives_ids.append(p_new_id);
+
+ tiles[p_atlas_coords].alternatives.erase(p_alternative_tile);
+ tiles[p_atlas_coords].alternatives_ids.erase(p_alternative_tile);
+ tiles[p_atlas_coords].alternatives_ids.sort();
+
+ emit_signal("changed");
+}
+
+bool TileSetAtlasSource::has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const {
+ ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), false, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords)));
+ return tiles[p_atlas_coords].alternatives.has(p_alternative_tile);
+}
+
+int TileSetAtlasSource::get_next_alternative_tile_id(const Vector2i p_atlas_coords) const {
+ ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), -1, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords)));
+ return tiles[p_atlas_coords].next_alternative_id;
}
-Vector2 TileSet::tile_get_navigation_polygon_offset(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Vector2());
- return tile_map[p_id].navigation_polygon_offset;
+int TileSetAtlasSource::get_alternative_tiles_count(const Vector2i p_atlas_coords) const {
+ ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), -1, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords)));
+ return tiles[p_atlas_coords].alternatives_ids.size();
}
-void TileSet::tile_set_navigation_polygon(int p_id, const Ref<NavigationPolygon> &p_navigation_polygon) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].navigation_polygon = p_navigation_polygon;
+int TileSetAtlasSource::get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const {
+ ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), -1, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords)));
+ ERR_FAIL_INDEX_V(p_index, tiles[p_atlas_coords].alternatives_ids.size(), -1);
+
+ return tiles[p_atlas_coords].alternatives_ids[p_index];
}
-Ref<NavigationPolygon> TileSet::tile_get_navigation_polygon(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Ref<NavigationPolygon>());
- return tile_map[p_id].navigation_polygon;
+Object *TileSetAtlasSource::get_tile_data(const Vector2i p_atlas_coords, int p_alternative_tile) const {
+ ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), nullptr, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords)));
+ ERR_FAIL_COND_V_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), nullptr, vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords)));
+
+ return tiles[p_atlas_coords].alternatives[p_alternative_tile];
}
-const Map<Vector2, Ref<OccluderPolygon2D>> &TileSet::autotile_get_light_oclusion_map(int p_id) const {
- static Map<Vector2, Ref<OccluderPolygon2D>> dummy;
- ERR_FAIL_COND_V(!tile_map.has(p_id), dummy);
- return tile_map[p_id].autotile_data.occluder_map;
+void TileSetAtlasSource::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_texture", "texture"), &TileSetAtlasSource::set_texture);
+ ClassDB::bind_method(D_METHOD("get_texture"), &TileSetAtlasSource::get_texture);
+ ClassDB::bind_method(D_METHOD("set_margins", "margins"), &TileSetAtlasSource::set_margins);
+ ClassDB::bind_method(D_METHOD("get_margins"), &TileSetAtlasSource::get_margins);
+ ClassDB::bind_method(D_METHOD("set_separation", "separation"), &TileSetAtlasSource::set_separation);
+ ClassDB::bind_method(D_METHOD("get_separation"), &TileSetAtlasSource::get_separation);
+ ClassDB::bind_method(D_METHOD("set_texture_region_size", "texture_region_size"), &TileSetAtlasSource::set_texture_region_size);
+ ClassDB::bind_method(D_METHOD("get_texture_region_size"), &TileSetAtlasSource::get_texture_region_size);
+
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_NOEDITOR), "set_texture", "get_texture");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "margins", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_margins", "get_margins");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_separation", "get_separation");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "tile_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_texture_region_size", "get_texture_region_size");
+
+ // Base tiles
+ ClassDB::bind_method(D_METHOD("create_tile", "atlas_coords", "size"), &TileSetAtlasSource::create_tile, DEFVAL(Vector2i(1, 1)));
+ ClassDB::bind_method(D_METHOD("remove_tile", "atlas_coords"), &TileSetAtlasSource::remove_tile); // Remove a tile. If p_tile_key.alternative_tile if different from 0, remove the alternative
+ ClassDB::bind_method(D_METHOD("has_tile", "atlas_coords"), &TileSetAtlasSource::has_tile);
+ ClassDB::bind_method(D_METHOD("can_move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::can_move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1)));
+ ClassDB::bind_method(D_METHOD("move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1)));
+ ClassDB::bind_method(D_METHOD("get_tile_size_in_atlas", "atlas_coords"), &TileSetAtlasSource::get_tile_size_in_atlas);
+
+ ClassDB::bind_method(D_METHOD("get_tiles_count"), &TileSetAtlasSource::get_tiles_count);
+ ClassDB::bind_method(D_METHOD("get_tile_id", "index"), &TileSetAtlasSource::get_tile_id);
+
+ ClassDB::bind_method(D_METHOD("get_tile_at_coords", "atlas_coords"), &TileSetAtlasSource::get_tile_at_coords);
+
+ // Alternative tiles
+ ClassDB::bind_method(D_METHOD("create_alternative_tile", "atlas_coords", "alternative_id_override"), &TileSetAtlasSource::create_alternative_tile, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("remove_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::remove_alternative_tile);
+ ClassDB::bind_method(D_METHOD("set_alternative_tile_id", "atlas_coords", "alternative_tile", "new_id"), &TileSetAtlasSource::set_alternative_tile_id);
+ ClassDB::bind_method(D_METHOD("has_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::has_alternative_tile);
+ ClassDB::bind_method(D_METHOD("get_next_alternative_tile_id", "atlas_coords"), &TileSetAtlasSource::get_next_alternative_tile_id);
+
+ ClassDB::bind_method(D_METHOD("get_alternative_tiles_count", "atlas_coords"), &TileSetAtlasSource::get_alternative_tiles_count);
+ ClassDB::bind_method(D_METHOD("get_alternative_tile_id", "atlas_coords", "index"), &TileSetAtlasSource::get_alternative_tile_id);
+
+ ClassDB::bind_method(D_METHOD("get_tile_data", "atlas_coords", "index"), &TileSetAtlasSource::get_tile_data);
+
+ // Helpers.
+ ClassDB::bind_method(D_METHOD("get_atlas_grid_size"), &TileSetAtlasSource::get_atlas_grid_size);
+ ClassDB::bind_method(D_METHOD("has_tiles_outside_texture"), &TileSetAtlasSource::has_tiles_outside_texture);
+ ClassDB::bind_method(D_METHOD("clear_tiles_outside_texture"), &TileSetAtlasSource::clear_tiles_outside_texture);
+ ClassDB::bind_method(D_METHOD("get_tile_texture_region", "atlas_coords"), &TileSetAtlasSource::get_tile_texture_region);
}
-void TileSet::autotile_set_navigation_polygon(int p_id, const Ref<NavigationPolygon> &p_navigation_polygon, const Vector2 &p_coord) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- if (p_navigation_polygon.is_null()) {
- if (tile_map[p_id].autotile_data.navpoly_map.has(p_coord)) {
- tile_map[p_id].autotile_data.navpoly_map.erase(p_coord);
+TileSetAtlasSource::~TileSetAtlasSource() {
+ // Free everything needed.
+ for (Map<Vector2i, TileAlternativesData>::Element *E_alternatives = tiles.front(); E_alternatives; E_alternatives = E_alternatives->next()) {
+ for (Map<int, TileData *>::Element *E_tile_data = E_alternatives->get().alternatives.front(); E_tile_data; E_tile_data = E_tile_data->next()) {
+ memdelete(E_tile_data->get());
}
- } else {
- tile_map[p_id].autotile_data.navpoly_map[p_coord] = p_navigation_polygon;
}
}
-Ref<NavigationPolygon> TileSet::autotile_get_navigation_polygon(int p_id, const Vector2 &p_coord) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Ref<NavigationPolygon>());
- if (!tile_map[p_id].autotile_data.navpoly_map.has(p_coord)) {
- return Ref<NavigationPolygon>();
- } else {
- return tile_map[p_id].autotile_data.navpoly_map[p_coord];
+TileData *TileSetAtlasSource::_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile) {
+ ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), nullptr, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
+ ERR_FAIL_COND_V_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), nullptr, vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords)));
+
+ return tiles[p_atlas_coords].alternatives[p_alternative_tile];
+}
+
+const TileData *TileSetAtlasSource::_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile) const {
+ ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), nullptr, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
+ ERR_FAIL_COND_V_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), nullptr, vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords)));
+
+ return tiles[p_atlas_coords].alternatives[p_alternative_tile];
+}
+
+void TileSetAtlasSource::_compute_next_alternative_id(const Vector2i p_atlas_coords) {
+ ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
+
+ while (tiles[p_atlas_coords].alternatives.has(tiles[p_atlas_coords].next_alternative_id)) {
+ tiles[p_atlas_coords].next_alternative_id = (tiles[p_atlas_coords].next_alternative_id % 1073741823) + 1; // 2 ** 30
+ };
+}
+
+/////////////////////////////// TileData //////////////////////////////////////
+
+void TileData::set_tile_set(const TileSet *p_tile_set) {
+ tile_set = p_tile_set;
+ if (tile_set) {
+ occluders.resize(tile_set->get_occlusion_layers_count());
+ physics.resize(tile_set->get_physics_layers_count());
+ navigation.resize(tile_set->get_navigation_layers_count());
+ custom_data.resize(tile_set->get_custom_data_layers_count());
+ }
+ notify_property_list_changed();
+}
+
+void TileData::notify_tile_data_properties_should_change() {
+ occluders.resize(tile_set->get_occlusion_layers_count());
+ physics.resize(tile_set->get_physics_layers_count());
+ for (int bit_index = 0; bit_index < 16; bit_index++) {
+ if (terrain_set < 0 || terrain_peering_bits[bit_index] >= tile_set->get_terrains_count(terrain_set)) {
+ terrain_peering_bits[bit_index] = -1;
+ }
+ }
+ navigation.resize(tile_set->get_navigation_layers_count());
+
+ // Convert custom data to the new type.
+ custom_data.resize(tile_set->get_custom_data_layers_count());
+ for (int i = 0; i < custom_data.size(); i++) {
+ if (custom_data[i].get_type() != tile_set->get_custom_data_type(i)) {
+ Variant new_val;
+ Callable::CallError error;
+ if (Variant::can_convert(custom_data[i].get_type(), tile_set->get_custom_data_type(i))) {
+ const Variant *args[] = { &custom_data[i] };
+ Variant::construct(tile_set->get_custom_data_type(i), new_val, args, 1, error);
+ } else {
+ Variant::construct(tile_set->get_custom_data_type(i), new_val, nullptr, 0, error);
+ }
+ custom_data.write[i] = new_val;
+ }
}
+
+ notify_property_list_changed();
+ emit_signal("changed");
+}
+
+void TileData::reset_state() {
+ occluders.clear();
+ physics.clear();
+ navigation.clear();
+ custom_data.clear();
+}
+
+void TileData::set_allow_transform(bool p_allow_transform) {
+ allow_transform = p_allow_transform;
+}
+
+bool TileData::is_allowing_transform() const {
+ return allow_transform;
+}
+
+// Rendering
+void TileData::set_flip_h(bool p_flip_h) {
+ ERR_FAIL_COND_MSG(!allow_transform && p_flip_h, "Transform is only allowed for alternative tiles (with its alternative_id != 0)");
+ flip_h = p_flip_h;
+ emit_signal("changed");
+}
+bool TileData::get_flip_h() const {
+ return flip_h;
+}
+
+void TileData::set_flip_v(bool p_flip_v) {
+ ERR_FAIL_COND_MSG(!allow_transform && p_flip_v, "Transform is only allowed for alternative tiles (with its alternative_id != 0)");
+ flip_v = p_flip_v;
+ emit_signal("changed");
+}
+
+bool TileData::get_flip_v() const {
+ return flip_v;
+}
+
+void TileData::set_transpose(bool p_transpose) {
+ ERR_FAIL_COND_MSG(!allow_transform && p_transpose, "Transform is only allowed for alternative tiles (with its alternative_id != 0)");
+ transpose = p_transpose;
+ emit_signal("changed");
+}
+bool TileData::get_transpose() const {
+ return transpose;
+}
+
+void TileData::set_texture_offset(Vector2i p_texture_offset) {
+ tex_offset = p_texture_offset;
+ emit_signal("changed");
+}
+
+Vector2i TileData::get_texture_offset() const {
+ return tex_offset;
+}
+
+void TileData::tile_set_material(Ref<ShaderMaterial> p_material) {
+ material = p_material;
+ emit_signal("changed");
+}
+Ref<ShaderMaterial> TileData::tile_get_material() const {
+ return material;
+}
+
+void TileData::set_modulate(Color p_modulate) {
+ modulate = p_modulate;
+ emit_signal("changed");
+}
+Color TileData::get_modulate() const {
+ return modulate;
+}
+
+void TileData::set_z_index(int p_z_index) {
+ z_index = p_z_index;
+ emit_signal("changed");
+}
+int TileData::get_z_index() const {
+ return z_index;
+}
+
+void TileData::set_y_sort_origin(Vector2i p_y_sort_origin) {
+ y_sort_origin = p_y_sort_origin;
+ emit_signal("changed");
+}
+Vector2i TileData::get_y_sort_origin() const {
+ return y_sort_origin;
+}
+
+void TileData::set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon) {
+ ERR_FAIL_INDEX(p_layer_id, occluders.size());
+ occluders.write[p_layer_id] = p_occluder_polygon;
+ emit_signal("changed");
+}
+
+Ref<OccluderPolygon2D> TileData::get_occluder(int p_layer_id) const {
+ ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), Ref<OccluderPolygon2D>());
+ return occluders[p_layer_id];
}
-const Map<Vector2, Ref<NavigationPolygon>> &TileSet::autotile_get_navigation_map(int p_id) const {
- static Map<Vector2, Ref<NavigationPolygon>> dummy;
- ERR_FAIL_COND_V(!tile_map.has(p_id), dummy);
- return tile_map[p_id].autotile_data.navpoly_map;
+// Physics
+int TileData::get_collision_shapes_count(int p_layer_id) const {
+ ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0);
+ return physics[p_layer_id].shapes.size();
}
-void TileSet::tile_set_occluder_offset(int p_id, const Vector2 &p_offset) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].occluder_offset = p_offset;
+void TileData::set_collision_shapes_count(int p_layer_id, int p_shapes_count) {
+ ERR_FAIL_INDEX(p_layer_id, physics.size());
+ ERR_FAIL_COND(p_shapes_count < 0);
+ physics.write[p_layer_id].shapes.resize(p_shapes_count);
+ notify_property_list_changed();
+ emit_signal("changed");
+}
+
+void TileData::add_collision_shape(int p_layer_id) {
+ ERR_FAIL_INDEX(p_layer_id, physics.size());
+ physics.write[p_layer_id].shapes.push_back(PhysicsLayerTileData::ShapeTileData());
+ emit_signal("changed");
+}
+
+void TileData::remove_collision_shape(int p_layer_id, int p_shape_index) {
+ ERR_FAIL_INDEX(p_layer_id, physics.size());
+ ERR_FAIL_INDEX(p_shape_index, physics[p_layer_id].shapes.size());
+ physics.write[p_layer_id].shapes.remove(p_shape_index);
+ emit_signal("changed");
+}
+
+void TileData::set_collision_shape_shape(int p_layer_id, int p_shape_index, Ref<Shape2D> p_shape) {
+ ERR_FAIL_INDEX(p_layer_id, physics.size());
+ ERR_FAIL_INDEX(p_shape_index, physics[p_layer_id].shapes.size());
+ physics.write[p_layer_id].shapes.write[p_shape_index].shape = p_shape;
+ emit_signal("changed");
}
-Vector2 TileSet::tile_get_occluder_offset(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Vector2());
- return tile_map[p_id].occluder_offset;
+Ref<Shape2D> TileData::get_collision_shape_shape(int p_layer_id, int p_shape_index) const {
+ ERR_FAIL_INDEX_V(p_layer_id, physics.size(), Ref<Shape2D>());
+ ERR_FAIL_INDEX_V(p_shape_index, physics[p_layer_id].shapes.size(), Ref<Shape2D>());
+ return physics[p_layer_id].shapes[p_shape_index].shape;
}
-void TileSet::tile_set_shapes(int p_id, const Vector<ShapeData> &p_shapes) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].shapes_data = p_shapes;
- for (int i = 0; i < p_shapes.size(); i++) {
- _decompose_convex_shape(p_shapes[i].shape);
+void TileData::set_collision_shape_one_way(int p_layer_id, int p_shape_index, bool p_one_way) {
+ ERR_FAIL_INDEX(p_layer_id, physics.size());
+ ERR_FAIL_INDEX(p_shape_index, physics[p_layer_id].shapes.size());
+ physics.write[p_layer_id].shapes.write[p_shape_index].one_way = p_one_way;
+ emit_signal("changed");
+}
+
+bool TileData::is_collision_shape_one_way(int p_layer_id, int p_shape_index) const {
+ ERR_FAIL_INDEX_V(p_layer_id, physics.size(), false);
+ ERR_FAIL_INDEX_V(p_shape_index, physics[p_layer_id].shapes.size(), false);
+ return physics[p_layer_id].shapes[p_shape_index].one_way;
+}
+
+void TileData::set_collision_shape_one_way_margin(int p_layer_id, int p_shape_index, float p_one_way_margin) {
+ ERR_FAIL_INDEX(p_layer_id, physics.size());
+ ERR_FAIL_INDEX(p_shape_index, physics[p_layer_id].shapes.size());
+ physics.write[p_layer_id].shapes.write[p_shape_index].one_way_margin = p_one_way_margin;
+ emit_signal("changed");
+}
+
+float TileData::get_collision_shape_one_way_margin(int p_layer_id, int p_shape_index) const {
+ ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0.0);
+ ERR_FAIL_INDEX_V(p_shape_index, physics[p_layer_id].shapes.size(), 0.0);
+ return physics[p_layer_id].shapes[p_shape_index].one_way_margin;
+}
+
+// Terrain
+void TileData::set_terrain_set(int p_terrain_set) {
+ ERR_FAIL_COND(p_terrain_set < -1);
+ if (tile_set) {
+ ERR_FAIL_COND(p_terrain_set >= tile_set->get_terrain_sets_count());
}
- emit_changed();
+ terrain_set = p_terrain_set;
+ notify_property_list_changed();
+ emit_signal("changed");
}
-Vector<TileSet::ShapeData> TileSet::tile_get_shapes(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Vector<ShapeData>());
+int TileData::get_terrain_set() const {
+ return terrain_set;
+}
- return tile_map[p_id].shapes_data;
+void TileData::set_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit, int p_terrain_index) {
+ ERR_FAIL_COND(p_terrain_index < -1);
+ if (tile_set) {
+ ERR_FAIL_COND(p_terrain_index >= tile_set->get_terrains_count(terrain_set));
+ ERR_FAIL_COND(!is_valid_peering_bit_terrain(p_peering_bit));
+ }
+ terrain_peering_bits[p_peering_bit] = p_terrain_index;
+ emit_signal("changed");
}
-int TileSet::tile_get_z_index(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), 0);
- return tile_map[p_id].z_index;
+int TileData::get_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const {
+ return terrain_peering_bits[p_peering_bit];
}
-void TileSet::tile_set_z_index(int p_id, int p_z_index) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map[p_id].z_index = p_z_index;
- emit_changed();
+bool TileData::is_valid_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const {
+ ERR_FAIL_COND_V(!tile_set, false);
+
+ return tile_set->is_valid_peering_bit_terrain(terrain_set, p_peering_bit);
}
-void TileSet::_tile_set_shapes(int p_id, const Array &p_shapes) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- Vector<ShapeData> shapes_data;
- Transform2D default_transform = tile_get_shape_transform(p_id, 0);
- bool default_one_way = tile_get_shape_one_way(p_id, 0);
- Vector2 default_autotile_coord = Vector2();
- for (int i = 0; i < p_shapes.size(); i++) {
- ShapeData s = ShapeData();
+// Navigation
+void TileData::set_navigation_polygon(int p_layer_id, Ref<NavigationPolygon> p_navigation_polygon) {
+ ERR_FAIL_INDEX(p_layer_id, navigation.size());
+ navigation.write[p_layer_id] = p_navigation_polygon;
+ emit_signal("changed");
+}
- if (p_shapes[i].get_type() == Variant::OBJECT) {
- Ref<Shape2D> shape = p_shapes[i];
- if (shape.is_null()) {
- continue;
+Ref<NavigationPolygon> TileData::get_navigation_polygon(int p_layer_id) const {
+ ERR_FAIL_INDEX_V(p_layer_id, navigation.size(), Ref<NavigationPolygon>());
+ return navigation[p_layer_id];
+}
+
+// Misc
+void TileData::set_probability(float p_probability) {
+ ERR_FAIL_COND(p_probability <= 0.0);
+ probability = p_probability;
+ emit_signal("changed");
+}
+float TileData::get_probability() const {
+ return probability;
+}
+
+// Custom data
+void TileData::set_custom_data(String p_layer_name, Variant p_value) {
+ ERR_FAIL_COND(!tile_set);
+ int p_layer_id = tile_set->get_custom_data_layer_by_name(p_layer_name);
+ ERR_FAIL_COND_MSG(p_layer_id < 0, vformat("TileSet has no layer with name: %s", p_layer_name));
+ set_custom_data_by_layer_id(p_layer_id, p_value);
+}
+
+Variant TileData::get_custom_data(String p_layer_name) const {
+ ERR_FAIL_COND_V(!tile_set, Variant());
+ int p_layer_id = tile_set->get_custom_data_layer_by_name(p_layer_name);
+ ERR_FAIL_COND_V_MSG(p_layer_id < 0, Variant(), vformat("TileSet has no layer with name: %s", p_layer_name));
+ return get_custom_data_by_layer_id(p_layer_id);
+}
+
+void TileData::set_custom_data_by_layer_id(int p_layer_id, Variant p_value) {
+ ERR_FAIL_INDEX(p_layer_id, custom_data.size());
+ custom_data.write[p_layer_id] = p_value;
+ emit_signal("changed");
+}
+
+Variant TileData::get_custom_data_by_layer_id(int p_layer_id) const {
+ ERR_FAIL_INDEX_V(p_layer_id, custom_data.size(), Variant());
+ return custom_data[p_layer_id];
+}
+
+bool TileData::_set(const StringName &p_name, const Variant &p_value) {
+ Vector<String> components = String(p_name).split("/", true, 2);
+
+ if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_integer()) {
+ // Occlusion layers.
+ int layer_index = components[0].trim_prefix("occlusion_layer_").to_int();
+ ERR_FAIL_COND_V(layer_index < 0, false);
+ if (components[1] == "polygon") {
+ Ref<OccluderPolygon2D> polygon = p_value;
+ if (!polygon.is_valid()) {
+ return false;
}
- s.shape = shape;
- s.shape_transform = default_transform;
- s.one_way_collision = default_one_way;
- s.autotile_coord = default_autotile_coord;
- } else if (p_shapes[i].get_type() == Variant::DICTIONARY) {
- Dictionary d = p_shapes[i];
+ if (layer_index >= occluders.size()) {
+ if (tile_set) {
+ return false;
+ } else {
+ occluders.resize(layer_index + 1);
+ }
+ }
+ set_occluder(layer_index, polygon);
+ return true;
+ }
+ } else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_integer()) {
+ // Physics layers.
+ int layer_index = components[0].trim_prefix("physics_layer_").to_int();
+ ERR_FAIL_COND_V(layer_index < 0, false);
+ if (components.size() == 2 && components[1] == "shapes_count") {
+ if (p_value.get_type() != Variant::INT) {
+ return false;
+ }
- if (d.has("shape") && d["shape"].get_type() == Variant::OBJECT) {
- s.shape = d["shape"];
- _decompose_convex_shape(s.shape);
- } else {
- continue;
+ if (layer_index >= physics.size()) {
+ if (tile_set) {
+ return false;
+ } else {
+ physics.resize(layer_index + 1);
+ }
}
+ set_collision_shapes_count(layer_index, p_value);
+ return true;
+ } else if (components.size() == 3 && components[1].begins_with("shape_") && components[1].trim_prefix("shape_").is_valid_integer()) {
+ int shape_index = components[1].trim_prefix("shape_").to_int();
+ ERR_FAIL_COND_V(shape_index < 0, false);
+
+ if (components[2] == "shape" || components[2] == "one_way" || components[2] == "one_way_margin") {
+ if (layer_index >= physics.size()) {
+ if (tile_set) {
+ return false;
+ } else {
+ physics.resize(layer_index + 1);
+ }
+ }
- if (d.has("shape_transform") && d["shape_transform"].get_type() == Variant::TRANSFORM2D) {
- s.shape_transform = d["shape_transform"];
- } else if (d.has("shape_offset") && d["shape_offset"].get_type() == Variant::VECTOR2) {
- s.shape_transform = Transform2D(0, (Vector2)d["shape_offset"]);
- } else {
- s.shape_transform = default_transform;
+ if (shape_index >= physics[layer_index].shapes.size()) {
+ physics.write[layer_index].shapes.resize(shape_index + 1);
+ }
+ }
+ if (components[2] == "shape") {
+ Ref<Shape2D> shape = p_value;
+ set_collision_shape_shape(layer_index, shape_index, shape);
+ return true;
+ } else if (components[2] == "one_way") {
+ set_collision_shape_one_way(layer_index, shape_index, p_value);
+ return true;
+ } else if (components[2] == "one_way_margin") {
+ set_collision_shape_one_way_margin(layer_index, shape_index, p_value);
+ return true;
+ }
+ }
+ } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_integer()) {
+ // Navigation layers.
+ int layer_index = components[0].trim_prefix("navigation_layer_").to_int();
+ ERR_FAIL_COND_V(layer_index < 0, false);
+ if (components[1] == "polygon") {
+ Ref<NavigationPolygon> polygon = p_value;
+ if (!polygon.is_valid()) {
+ return false;
}
- if (d.has("one_way") && d["one_way"].get_type() == Variant::BOOL) {
- s.one_way_collision = d["one_way"];
+ if (layer_index >= navigation.size()) {
+ if (tile_set) {
+ return false;
+ } else {
+ navigation.resize(layer_index + 1);
+ }
+ }
+ set_navigation_polygon(layer_index, polygon);
+ return true;
+ }
+ } else if (components.size() == 2 && components[0] == "terrains_peering_bit") {
+ // Terrains.
+ if (components[1] == "right_side") {
+ set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_SIDE, p_value);
+ } else if (components[1] == "right_corner") {
+ set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_CORNER, p_value);
+ } else if (components[1] == "bottom_right_side") {
+ set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, p_value);
+ } else if (components[1] == "bottom_right_corner") {
+ set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER, p_value);
+ } else if (components[1] == "bottom_side") {
+ set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE, p_value);
+ } else if (components[1] == "bottom_corner") {
+ set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_CORNER, p_value);
+ } else if (components[1] == "bottom_left_side") {
+ set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, p_value);
+ } else if (components[1] == "bottom_left_corner") {
+ set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER, p_value);
+ } else if (components[1] == "left_side") {
+ set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_SIDE, p_value);
+ } else if (components[1] == "left_corner") {
+ set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_CORNER, p_value);
+ } else if (components[1] == "top_left_side") {
+ set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, p_value);
+ } else if (components[1] == "top_left_corner") {
+ set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER, p_value);
+ } else if (components[1] == "top_side") {
+ set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_SIDE, p_value);
+ } else if (components[1] == "top_corner") {
+ set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_CORNER, p_value);
+ } else if (components[1] == "top_right_side") {
+ set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, p_value);
+ } else if (components[1] == "top_right_corner") {
+ set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER, p_value);
+ } else {
+ return false;
+ }
+ return true;
+ } else if (components.size() == 1 && components[0].begins_with("custom_data_") && components[0].trim_prefix("custom_data_").is_valid_integer()) {
+ // Custom data layers.
+ int layer_index = components[0].trim_prefix("custom_data_").to_int();
+ ERR_FAIL_COND_V(layer_index < 0, false);
+
+ if (layer_index >= custom_data.size()) {
+ if (tile_set) {
+ return false;
} else {
- s.one_way_collision = default_one_way;
+ custom_data.resize(layer_index + 1);
}
+ }
+ set_custom_data_by_layer_id(layer_index, p_value);
+
+ return true;
+ }
+
+ return false;
+}
- if (d.has("one_way_margin") && d["one_way_margin"].is_num()) {
- s.one_way_collision_margin = d["one_way_margin"];
+bool TileData::_get(const StringName &p_name, Variant &r_ret) const {
+ Vector<String> components = String(p_name).split("/", true, 2);
+
+ if (tile_set) {
+ if (components.size() == 2 && components[0].begins_with("occlusion_layer") && components[0].trim_prefix("occlusion_layer_").is_valid_integer()) {
+ // Occlusion layers.
+ int layer_index = components[0].trim_prefix("occlusion_layer_").to_int();
+ ERR_FAIL_COND_V(layer_index < 0, false);
+ if (layer_index >= occluders.size()) {
+ return false;
+ }
+ if (components[1] == "polygon") {
+ r_ret = get_occluder(layer_index);
+ return true;
+ }
+ } else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_integer()) {
+ // Physics layers.
+ int layer_index = components[0].trim_prefix("physics_layer_").to_int();
+ ERR_FAIL_COND_V(layer_index < 0, false);
+ if (layer_index >= physics.size()) {
+ return false;
+ }
+ if (components.size() == 2 && components[1] == "shapes_count") {
+ r_ret = get_collision_shapes_count(layer_index);
+ return true;
+ } else if (components.size() == 3 && components[1].begins_with("shape_") && components[1].trim_prefix("shape_").is_valid_integer()) {
+ int shape_index = components[1].trim_prefix("shape_").to_int();
+ ERR_FAIL_COND_V(shape_index < 0, false);
+ if (shape_index >= physics[layer_index].shapes.size()) {
+ return false;
+ }
+ if (components[2] == "shape") {
+ r_ret = get_collision_shape_shape(layer_index, shape_index);
+ return true;
+ } else if (components[2] == "one_way") {
+ r_ret = is_collision_shape_one_way(layer_index, shape_index);
+ return true;
+ } else if (components[2] == "one_way_margin") {
+ r_ret = get_collision_shape_one_way_margin(layer_index, shape_index);
+ return true;
+ }
+ }
+ } else if (components.size() == 2 && components[0] == "terrains_peering_bit") {
+ // Terrains.
+ if (components[1] == "right_side") {
+ r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_RIGHT_SIDE];
+ } else if (components[1] == "right_corner") {
+ r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_RIGHT_CORNER];
+ } else if (components[1] == "bottom_right_side") {
+ r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE];
+ } else if (components[1] == "bottom_right_corner") {
+ r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER];
+ } else if (components[1] == "bottom_side") {
+ r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_BOTTOM_SIDE];
+ } else if (components[1] == "bottom_corner") {
+ r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_BOTTOM_CORNER];
+ } else if (components[1] == "bottom_left_side") {
+ r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE];
+ } else if (components[1] == "bottom_left_corner") {
+ r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER];
+ } else if (components[1] == "left_side") {
+ r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_LEFT_SIDE];
+ } else if (components[1] == "left_corner") {
+ r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_LEFT_CORNER];
+ } else if (components[1] == "top_left_side") {
+ r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE];
+ } else if (components[1] == "top_left_corner") {
+ r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER];
+ } else if (components[1] == "top_side") {
+ r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_TOP_SIDE];
+ } else if (components[1] == "top_corner") {
+ r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_TOP_CORNER];
+ } else if (components[1] == "top_right_side") {
+ r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE];
+ } else if (components[1] == "top_right_corner") {
+ r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER];
} else {
- s.one_way_collision_margin = 1.0;
+ return false;
+ }
+ return true;
+ } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_integer()) {
+ // Occlusion layers.
+ int layer_index = components[0].trim_prefix("navigation_layer_").to_int();
+ ERR_FAIL_COND_V(layer_index < 0, false);
+ if (layer_index >= navigation.size()) {
+ return false;
+ }
+ if (components[1] == "polygon") {
+ r_ret = get_navigation_polygon(layer_index);
+ return true;
+ }
+ } else if (components.size() == 1 && components[0].begins_with("custom_data_") && components[0].trim_prefix("custom_data_").is_valid_integer()) {
+ // Custom data layers.
+ int layer_index = components[0].trim_prefix("custom_data_").to_int();
+ ERR_FAIL_COND_V(layer_index < 0, false);
+ if (layer_index >= custom_data.size()) {
+ return false;
}
+ r_ret = get_custom_data_by_layer_id(layer_index);
+ return true;
+ }
+ }
- if (d.has("autotile_coord") && d["autotile_coord"].get_type() == Variant::VECTOR2) {
- s.autotile_coord = d["autotile_coord"];
- } else {
- s.autotile_coord = default_autotile_coord;
+ return false;
+}
+
+void TileData::_get_property_list(List<PropertyInfo> *p_list) const {
+ PropertyInfo property_info;
+ // Add the groups manually.
+ if (tile_set) {
+ // Occlusion layers.
+ p_list->push_back(PropertyInfo(Variant::NIL, "Rendering", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
+ for (int i = 0; i < occluders.size(); i++) {
+ // occlusion_layer_%d/polygon
+ property_info = PropertyInfo(Variant::OBJECT, vformat("occlusion_layer_%d/polygon", i), PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_DEFAULT);
+ if (!occluders[i].is_valid()) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
}
+ p_list->push_back(property_info);
+ }
- } else {
- ERR_CONTINUE_MSG(true, "Expected an array of objects or dictionaries for tile_set_shapes.");
+ // Physics layers.
+ p_list->push_back(PropertyInfo(Variant::NIL, "Physics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
+ for (int i = 0; i < physics.size(); i++) {
+ p_list->push_back(PropertyInfo(Variant::INT, vformat("physics_layer_%d/shapes_count", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR));
+
+ for (int j = 0; j < physics[i].shapes.size(); j++) {
+ // physics_layer_%d/shapes_count
+ property_info = PropertyInfo(Variant::OBJECT, vformat("physics_layer_%d/shape_%d/shape", i, j), PROPERTY_HINT_RESOURCE_TYPE, "Shape2D", PROPERTY_USAGE_DEFAULT);
+ if (!physics[i].shapes[j].shape.is_valid()) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+
+ // physics_layer_%d/shape_%d/one_way
+ property_info = PropertyInfo(Variant::BOOL, vformat("physics_layer_%d/shape_%d/one_way", i, j));
+ if (physics[i].shapes[j].one_way == false) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+
+ // physics_layer_%d/shape_%d/one_way_margin
+ property_info = PropertyInfo(Variant::FLOAT, vformat("physics_layer_%d/shape_%d/one_way_margin", i, j));
+ if (physics[i].shapes[j].one_way_margin == 1.0) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
+ }
+
+ // Terrain data
+ if (terrain_set >= 0) {
+ p_list->push_back(PropertyInfo(Variant::NIL, "Terrains", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
+ if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) {
+ property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/right_side");
+ if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_SIDE) == -1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
+ if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_CORNER)) {
+ property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/right_corner");
+ if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_CORNER) == -1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
+ if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)) {
+ property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/bottom_right_side");
+ if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) == -1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
+ if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)) {
+ property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/bottom_right_corner");
+ if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER) == -1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
+ if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) {
+ property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/bottom_side");
+ if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE) == -1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
+ if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)) {
+ property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/bottom_corner");
+ if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) == -1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
+ if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)) {
+ property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/bottom_left_side");
+ if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) == -1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
+ if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER)) {
+ property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/bottom_left_corner");
+ if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER) == -1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
+ if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
+ property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/left_side");
+ if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_SIDE) == -1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
+ if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_CORNER)) {
+ property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/left_corner");
+ if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_CORNER) == -1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
+ if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE)) {
+ property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/top_left_side");
+ if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) == -1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
+ if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER)) {
+ property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/top_left_corner");
+ if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER) == -1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
+ if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
+ property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/top_side");
+ if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_SIDE) == -1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
+ if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_CORNER)) {
+ property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/top_corner");
+ if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_CORNER) == -1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
+ if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)) {
+ property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/top_right_side");
+ if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) == -1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
+ if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER)) {
+ property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/top_right_corner");
+ if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER) == -1) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
}
- shapes_data.push_back(s);
+ // Navigation layers.
+ p_list->push_back(PropertyInfo(Variant::NIL, "Navigation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
+ for (int i = 0; i < navigation.size(); i++) {
+ property_info = PropertyInfo(Variant::OBJECT, vformat("navigation_layer_%d/polygon", i), PROPERTY_HINT_RESOURCE_TYPE, "NavigationPolygon", PROPERTY_USAGE_DEFAULT);
+ if (!navigation[i].is_valid()) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
+
+ // Custom data layers.
+ p_list->push_back(PropertyInfo(Variant::NIL, "Custom data", PROPERTY_HINT_NONE, "custom_data_", PROPERTY_USAGE_GROUP));
+ for (int i = 0; i < custom_data.size(); i++) {
+ Variant default_val;
+ Callable::CallError error;
+ Variant::construct(custom_data[i].get_type(), default_val, nullptr, 0, error);
+ property_info = PropertyInfo(tile_set->get_custom_data_type(i), vformat("custom_data_%d", i), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT);
+ if (custom_data[i] == default_val) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+ }
}
+}
- tile_map[p_id].shapes_data = shapes_data;
- emit_changed();
+void TileData::_bind_methods() {
+ // Rendering.
+ ClassDB::bind_method(D_METHOD("set_flip_h", "flip_h"), &TileData::set_flip_h);
+ ClassDB::bind_method(D_METHOD("get_flip_h"), &TileData::get_flip_h);
+ ClassDB::bind_method(D_METHOD("set_flip_v", "flip_v"), &TileData::set_flip_v);
+ ClassDB::bind_method(D_METHOD("get_flip_v"), &TileData::get_flip_v);
+ ClassDB::bind_method(D_METHOD("set_transpose", "transpose"), &TileData::set_transpose);
+ ClassDB::bind_method(D_METHOD("get_transpose"), &TileData::get_transpose);
+ ClassDB::bind_method(D_METHOD("tile_set_material", "material"), &TileData::tile_set_material);
+ ClassDB::bind_method(D_METHOD("tile_get_material"), &TileData::tile_get_material);
+ ClassDB::bind_method(D_METHOD("set_texture_offset", "texture_offset"), &TileData::set_texture_offset);
+ ClassDB::bind_method(D_METHOD("get_texture_offset"), &TileData::get_texture_offset);
+ ClassDB::bind_method(D_METHOD("set_modulate", "modulate"), &TileData::set_modulate);
+ ClassDB::bind_method(D_METHOD("get_modulate"), &TileData::get_modulate);
+ ClassDB::bind_method(D_METHOD("set_z_index", "z_index"), &TileData::set_z_index);
+ ClassDB::bind_method(D_METHOD("get_z_index"), &TileData::get_z_index);
+ ClassDB::bind_method(D_METHOD("set_y_sort_origin", "y_sort_origin"), &TileData::set_y_sort_origin);
+ ClassDB::bind_method(D_METHOD("get_y_sort_origin"), &TileData::get_y_sort_origin);
+
+ ClassDB::bind_method(D_METHOD("set_occluder", "layer_id", "occluder_polygon"), &TileData::set_occluder);
+ ClassDB::bind_method(D_METHOD("get_occluder", "layer_id"), &TileData::get_occluder);
+
+ // Physics.
+ ClassDB::bind_method(D_METHOD("get_collision_shapes_count", "layer_id"), &TileData::get_collision_shapes_count);
+ ClassDB::bind_method(D_METHOD("set_collision_shapes_count", "layer_id", "shapes_count"), &TileData::set_collision_shapes_count);
+ ClassDB::bind_method(D_METHOD("add_collision_shape", "layer_id"), &TileData::add_collision_shape);
+ ClassDB::bind_method(D_METHOD("remove_collision_shape", "layer_id", "shape_index"), &TileData::remove_collision_shape);
+ ClassDB::bind_method(D_METHOD("set_collision_shape_shape", "layer_id", "shape_index", "shape"), &TileData::set_collision_shape_shape);
+ ClassDB::bind_method(D_METHOD("get_collision_shape_shape", "layer_id", "shape_index"), &TileData::get_collision_shape_shape);
+ ClassDB::bind_method(D_METHOD("set_collision_shape_one_way", "layer_id", "shape_index", "one_way"), &TileData::set_collision_shape_one_way);
+ ClassDB::bind_method(D_METHOD("is_collision_shape_one_way", "layer_id", "shape_index"), &TileData::is_collision_shape_one_way);
+ ClassDB::bind_method(D_METHOD("set_collision_shape_one_way_margin", "layer_id", "shape_index", "one_way_margin"), &TileData::set_collision_shape_one_way_margin);
+ ClassDB::bind_method(D_METHOD("get_collision_shape_one_way_margin", "layer_id", "shape_index"), &TileData::get_collision_shape_one_way_margin);
+
+ // Terrain
+ ClassDB::bind_method(D_METHOD("set_terrain_set", "terrain_set"), &TileData::set_terrain_set);
+ ClassDB::bind_method(D_METHOD("get_terrain_set"), &TileData::get_terrain_set);
+ ClassDB::bind_method(D_METHOD("set_peering_bit_terrain", "peering_bit", "terrain"), &TileData::set_peering_bit_terrain);
+ ClassDB::bind_method(D_METHOD("get_peering_bit_terrain", "peering_bit"), &TileData::get_peering_bit_terrain);
+
+ // Navigation
+ ClassDB::bind_method(D_METHOD("set_navigation_polygon", "layer_id", "navigation_polygon"), &TileData::set_navigation_polygon);
+ ClassDB::bind_method(D_METHOD("get_navigation_polygon", "layer_id"), &TileData::get_navigation_polygon);
+
+ // Misc.
+ ClassDB::bind_method(D_METHOD("set_probability", "probability"), &TileData::set_probability);
+ ClassDB::bind_method(D_METHOD("get_probability"), &TileData::get_probability);
+
+ // Custom data.
+ ClassDB::bind_method(D_METHOD("set_custom_data", "layer_name", "value"), &TileData::set_custom_data);
+ ClassDB::bind_method(D_METHOD("get_custom_data", "layer_name"), &TileData::get_custom_data);
+ ClassDB::bind_method(D_METHOD("set_custom_data_by_layer_id", "layer_id", "value"), &TileData::set_custom_data_by_layer_id);
+ ClassDB::bind_method(D_METHOD("get_custom_data_by_layer_id", "layer_id"), &TileData::get_custom_data_by_layer_id);
+
+ ADD_GROUP("Rendering", "");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h"), "set_flip_h", "get_flip_h");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v"), "set_flip_v", "get_flip_v");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "transpose"), "set_transpose", "get_transpose");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_offset"), "set_texture_offset", "get_texture_offset");
+ ADD_PROPERTY(PropertyInfo(Variant::COLOR, "modulate"), "set_modulate", "get_modulate");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "z_index"), "set_z_index", "get_z_index");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "y_sort_origin"), "set_y_sort_origin", "get_y_sort_origin");
+
+ ADD_GROUP("Terrains", "");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "terrain_set"), "set_terrain_set", "get_terrain_set");
+
+ ADD_GROUP("Miscellaneous", "");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "probability"), "set_probability", "get_probability");
+
+ ADD_SIGNAL(MethodInfo("changed"));
}
-Array TileSet::_tile_get_shapes(int p_id) const {
- ERR_FAIL_COND_V(!tile_map.has(p_id), Array());
- Array arr;
+/////////////////////////////// TileSetAtlasPluginTerrain //////////////////////////////////////
+
+// --- PLUGINS ---
+void TileSetAtlasPluginTerrain::_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) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_SIDE:
+ bit_rect.position = Vector2(1, -1);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ bit_rect.position = Vector2(1, 1);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE:
+ bit_rect.position = Vector2(-1, 1);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ bit_rect.position = Vector2(-3, 1);
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_SIDE:
+ bit_rect.position = Vector2(-3, -1);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ bit_rect.position = Vector2(-3, -3);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_SIDE:
+ bit_rect.position = Vector2(-1, -3);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ bit_rect.position = Vector2(1, -3);
+ break;
+ default:
+ break;
+ }
+ bit_rect.position *= Vector2(p_size) / 6.0;
+ p_canvas_item->draw_rect(bit_rect, p_color);
+}
- Vector<ShapeData> data = tile_map[p_id].shapes_data;
- for (int i = 0; i < data.size(); i++) {
- Dictionary shape_data;
- shape_data["shape"] = data[i].shape;
- shape_data["shape_transform"] = data[i].shape_transform;
- shape_data["one_way"] = data[i].one_way_collision;
- shape_data["one_way_margin"] = data[i].one_way_collision_margin;
- shape_data["autotile_coord"] = data[i].autotile_coord;
- arr.push_back(shape_data);
+void TileSetAtlasPluginTerrain::_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);
+
+ Vector2 unit = Vector2(p_size) / 6.0;
+ PackedVector2Array polygon;
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ polygon.push_back(Vector2(0, 3) * unit);
+ polygon.push_back(Vector2(3, 3) * unit);
+ polygon.push_back(Vector2(3, 0) * unit);
+ polygon.push_back(Vector2(1, 0) * unit);
+ polygon.push_back(Vector2(1, 1) * unit);
+ polygon.push_back(Vector2(0, 1) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ polygon.push_back(Vector2(0, 3) * unit);
+ polygon.push_back(Vector2(-3, 3) * unit);
+ polygon.push_back(Vector2(-3, 0) * unit);
+ polygon.push_back(Vector2(-1, 0) * unit);
+ polygon.push_back(Vector2(-1, 1) * unit);
+ polygon.push_back(Vector2(0, 1) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ polygon.push_back(Vector2(0, -3) * unit);
+ polygon.push_back(Vector2(-3, -3) * unit);
+ polygon.push_back(Vector2(-3, 0) * unit);
+ polygon.push_back(Vector2(-1, 0) * unit);
+ polygon.push_back(Vector2(-1, -1) * unit);
+ polygon.push_back(Vector2(0, -1) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ polygon.push_back(Vector2(0, -3) * unit);
+ polygon.push_back(Vector2(3, -3) * unit);
+ polygon.push_back(Vector2(3, 0) * unit);
+ polygon.push_back(Vector2(1, 0) * unit);
+ polygon.push_back(Vector2(1, -1) * unit);
+ polygon.push_back(Vector2(0, -1) * unit);
+ break;
+ default:
+ break;
+ }
+ if (!polygon.is_empty()) {
+ p_canvas_item->draw_polygon(polygon, color_array);
}
+}
- return arr;
+void TileSetAtlasPluginTerrain::_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);
+
+ Vector2 unit = Vector2(p_size) / 6.0;
+ PackedVector2Array polygon;
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_SIDE:
+ polygon.push_back(Vector2(1, -1) * unit);
+ polygon.push_back(Vector2(3, -3) * unit);
+ polygon.push_back(Vector2(3, 3) * unit);
+ polygon.push_back(Vector2(1, 1) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE:
+ polygon.push_back(Vector2(-1, 1) * unit);
+ polygon.push_back(Vector2(-3, 3) * unit);
+ polygon.push_back(Vector2(3, 3) * unit);
+ polygon.push_back(Vector2(1, 1) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_SIDE:
+ polygon.push_back(Vector2(-1, -1) * unit);
+ polygon.push_back(Vector2(-3, -3) * unit);
+ polygon.push_back(Vector2(-3, 3) * unit);
+ polygon.push_back(Vector2(-1, 1) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_SIDE:
+ polygon.push_back(Vector2(-1, -1) * unit);
+ polygon.push_back(Vector2(-3, -3) * unit);
+ polygon.push_back(Vector2(3, -3) * unit);
+ polygon.push_back(Vector2(1, -1) * unit);
+ break;
+ default:
+ break;
+ }
+ if (!polygon.is_empty()) {
+ p_canvas_item->draw_polygon(polygon, color_array);
+ }
}
-Array TileSet::_get_tiles_ids() const {
- Array arr;
+void TileSetAtlasPluginTerrain::_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);
+
+ Vector2 unit = Vector2(p_size) / 6.0;
+ PackedVector2Array polygon;
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_CORNER:
+ polygon.push_back(Vector2(1, 0) * unit);
+ polygon.push_back(Vector2(2, -1) * unit);
+ polygon.push_back(Vector2(3, 0) * unit);
+ polygon.push_back(Vector2(2, 1) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ polygon.push_back(Vector2(0, 1) * unit);
+ polygon.push_back(Vector2(1, 2) * unit);
+ polygon.push_back(Vector2(2, 1) * unit);
+ polygon.push_back(Vector2(1, 0) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER:
+ polygon.push_back(Vector2(0, 1) * unit);
+ polygon.push_back(Vector2(-1, 2) * unit);
+ polygon.push_back(Vector2(0, 3) * unit);
+ polygon.push_back(Vector2(1, 2) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ polygon.push_back(Vector2(0, 1) * unit);
+ polygon.push_back(Vector2(-1, 2) * unit);
+ polygon.push_back(Vector2(-2, 1) * unit);
+ polygon.push_back(Vector2(-1, 0) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_CORNER:
+ polygon.push_back(Vector2(-1, 0) * unit);
+ polygon.push_back(Vector2(-2, -1) * unit);
+ polygon.push_back(Vector2(-3, 0) * unit);
+ polygon.push_back(Vector2(-2, 1) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ polygon.push_back(Vector2(0, -1) * unit);
+ polygon.push_back(Vector2(-1, -2) * unit);
+ polygon.push_back(Vector2(-2, -1) * unit);
+ polygon.push_back(Vector2(-1, 0) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_CORNER:
+ polygon.push_back(Vector2(0, -1) * unit);
+ polygon.push_back(Vector2(-1, -2) * unit);
+ polygon.push_back(Vector2(0, -3) * unit);
+ polygon.push_back(Vector2(1, -2) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ polygon.push_back(Vector2(0, -1) * unit);
+ polygon.push_back(Vector2(1, -2) * unit);
+ polygon.push_back(Vector2(2, -1) * unit);
+ polygon.push_back(Vector2(1, 0) * unit);
+ break;
+ default:
+ break;
+ }
+ if (!polygon.is_empty()) {
+ p_canvas_item->draw_polygon(polygon, color_array);
+ }
+}
- for (Map<int, TileData>::Element *E = tile_map.front(); E; E = E->next()) {
- arr.push_back(E->key());
+void TileSetAtlasPluginTerrain::_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);
+
+ Vector2 unit = Vector2(p_size) / 6.0;
+ PackedVector2Array polygon;
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_CORNER:
+ polygon.push_back(Vector2(0.5, -0.5) * unit);
+ polygon.push_back(Vector2(1.5, -1.5) * unit);
+ polygon.push_back(Vector2(3, 0) * unit);
+ polygon.push_back(Vector2(1.5, 1.5) * unit);
+ polygon.push_back(Vector2(0.5, 0.5) * unit);
+ polygon.push_back(Vector2(1, 0) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER:
+ polygon.push_back(Vector2(-0.5, 0.5) * unit);
+ polygon.push_back(Vector2(-1.5, 1.5) * unit);
+ polygon.push_back(Vector2(0, 3) * unit);
+ polygon.push_back(Vector2(1.5, 1.5) * unit);
+ polygon.push_back(Vector2(0.5, 0.5) * unit);
+ polygon.push_back(Vector2(0, 1) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_CORNER:
+ polygon.push_back(Vector2(-0.5, -0.5) * unit);
+ polygon.push_back(Vector2(-1.5, -1.5) * unit);
+ polygon.push_back(Vector2(-3, 0) * unit);
+ polygon.push_back(Vector2(-1.5, 1.5) * unit);
+ polygon.push_back(Vector2(-0.5, 0.5) * unit);
+ polygon.push_back(Vector2(-1, 0) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_CORNER:
+ polygon.push_back(Vector2(-0.5, -0.5) * unit);
+ polygon.push_back(Vector2(-1.5, -1.5) * unit);
+ polygon.push_back(Vector2(0, -3) * unit);
+ polygon.push_back(Vector2(1.5, -1.5) * unit);
+ polygon.push_back(Vector2(0.5, -0.5) * unit);
+ polygon.push_back(Vector2(0, -1) * unit);
+ break;
+ default:
+ break;
}
+ if (!polygon.is_empty()) {
+ p_canvas_item->draw_polygon(polygon, color_array);
+ }
+}
- return arr;
+void TileSetAtlasPluginTerrain::_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);
+
+ Vector2 unit = Vector2(p_size) / 6.0;
+ PackedVector2Array polygon;
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ polygon.push_back(Vector2(1, 0) * unit);
+ polygon.push_back(Vector2(3, 0) * unit);
+ polygon.push_back(Vector2(0, 3) * unit);
+ polygon.push_back(Vector2(0, 1) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ polygon.push_back(Vector2(-1, 0) * unit);
+ polygon.push_back(Vector2(-3, 0) * unit);
+ polygon.push_back(Vector2(0, 3) * unit);
+ polygon.push_back(Vector2(0, 1) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ polygon.push_back(Vector2(-1, 0) * unit);
+ polygon.push_back(Vector2(-3, 0) * unit);
+ polygon.push_back(Vector2(0, -3) * unit);
+ polygon.push_back(Vector2(0, -1) * unit);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ polygon.push_back(Vector2(1, 0) * unit);
+ polygon.push_back(Vector2(3, 0) * unit);
+ polygon.push_back(Vector2(0, -3) * unit);
+ polygon.push_back(Vector2(0, -1) * unit);
+ break;
+ default:
+ break;
+ }
+ if (!polygon.is_empty()) {
+ p_canvas_item->draw_polygon(polygon, color_array);
+ }
}
-void TileSet::_decompose_convex_shape(Ref<Shape2D> p_shape) {
- if (Engine::get_singleton()->is_editor_hint()) {
- return;
+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) {
+ PackedColorArray color_array;
+ color_array.push_back(p_color);
+
+ PackedVector2Array point_list;
+ point_list.push_back(Vector2(3, (3.0 * (1.0 - p_overlap * 2.0)) / 2.0));
+ point_list.push_back(Vector2(3, 3.0 * (1.0 - p_overlap * 2.0)));
+ point_list.push_back(Vector2(2, 3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0)));
+ point_list.push_back(Vector2(1, 3.0 - p_overlap * 2.0));
+ point_list.push_back(Vector2(0, 3));
+ point_list.push_back(Vector2(-1, 3.0 - p_overlap * 2.0));
+ point_list.push_back(Vector2(-2, 3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0)));
+ point_list.push_back(Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0)));
+ point_list.push_back(Vector2(-3, (3.0 * (1.0 - p_overlap * 2.0)) / 2.0));
+ point_list.push_back(Vector2(-3, -(3.0 * (1.0 - p_overlap * 2.0)) / 2.0));
+ point_list.push_back(Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0)));
+ point_list.push_back(Vector2(-2, -3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0)));
+ point_list.push_back(Vector2(-1, -(3.0 - p_overlap * 2.0)));
+ point_list.push_back(Vector2(0, -3));
+ point_list.push_back(Vector2(1, -(3.0 - p_overlap * 2.0)));
+ point_list.push_back(Vector2(2, -3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0)));
+ point_list.push_back(Vector2(3, -3.0 * (1.0 - p_overlap * 2.0)));
+ point_list.push_back(Vector2(3, -(3.0 * (1.0 - p_overlap * 2.0)) / 2.0));
+
+ Vector2 unit = Vector2(p_size) / 6.0;
+ for (int i = 0; i < point_list.size(); i++) {
+ point_list.write[i] = point_list[i] * unit;
+ }
+
+ PackedVector2Array polygon;
+ if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_SIDE:
+ polygon.push_back(point_list[17]);
+ polygon.push_back(point_list[0]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ polygon.push_back(point_list[0]);
+ polygon.push_back(point_list[1]);
+ polygon.push_back(point_list[2]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ polygon.push_back(point_list[2]);
+ polygon.push_back(point_list[3]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER:
+ polygon.push_back(point_list[3]);
+ polygon.push_back(point_list[4]);
+ polygon.push_back(point_list[5]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ polygon.push_back(point_list[5]);
+ polygon.push_back(point_list[6]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ polygon.push_back(point_list[6]);
+ polygon.push_back(point_list[7]);
+ polygon.push_back(point_list[8]);
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_SIDE:
+ polygon.push_back(point_list[8]);
+ polygon.push_back(point_list[9]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ polygon.push_back(point_list[9]);
+ polygon.push_back(point_list[10]);
+ polygon.push_back(point_list[11]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ polygon.push_back(point_list[11]);
+ polygon.push_back(point_list[12]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_CORNER:
+ polygon.push_back(point_list[12]);
+ polygon.push_back(point_list[13]);
+ polygon.push_back(point_list[14]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ polygon.push_back(point_list[14]);
+ polygon.push_back(point_list[15]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ polygon.push_back(point_list[15]);
+ polygon.push_back(point_list[16]);
+ polygon.push_back(point_list[17]);
+ break;
+ default:
+ break;
+ }
+ } else {
+ if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) {
+ for (int i = 0; i < point_list.size(); i++) {
+ point_list.write[i] = Vector2(point_list[i].y, point_list[i].x);
+ }
+ }
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_CORNER:
+ polygon.push_back(point_list[3]);
+ polygon.push_back(point_list[4]);
+ polygon.push_back(point_list[5]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ polygon.push_back(point_list[2]);
+ polygon.push_back(point_list[3]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ polygon.push_back(point_list[0]);
+ polygon.push_back(point_list[1]);
+ polygon.push_back(point_list[2]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE:
+ polygon.push_back(point_list[17]);
+ polygon.push_back(point_list[0]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ polygon.push_back(point_list[15]);
+ polygon.push_back(point_list[16]);
+ polygon.push_back(point_list[17]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ polygon.push_back(point_list[14]);
+ polygon.push_back(point_list[15]);
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_CORNER:
+ polygon.push_back(point_list[12]);
+ polygon.push_back(point_list[13]);
+ polygon.push_back(point_list[14]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ polygon.push_back(point_list[11]);
+ polygon.push_back(point_list[12]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ polygon.push_back(point_list[9]);
+ polygon.push_back(point_list[10]);
+ polygon.push_back(point_list[11]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_SIDE:
+ polygon.push_back(point_list[8]);
+ polygon.push_back(point_list[9]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ polygon.push_back(point_list[6]);
+ polygon.push_back(point_list[7]);
+ polygon.push_back(point_list[8]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ polygon.push_back(point_list[5]);
+ polygon.push_back(point_list[6]);
+ break;
+ default:
+ break;
+ }
+ }
+
+ int half_polygon_size = polygon.size();
+ for (int i = 0; i < half_polygon_size; i++) {
+ polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0);
+ }
+
+ if (!polygon.is_empty()) {
+ p_canvas_item->draw_polygon(polygon, color_array);
+ }
+}
+
+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) {
+ PackedColorArray color_array;
+ color_array.push_back(p_color);
+
+ PackedVector2Array point_list;
+ point_list.push_back(Vector2(3, 0));
+ point_list.push_back(Vector2(3, 3.0 * (1.0 - p_overlap * 2.0)));
+ point_list.push_back(Vector2(1.5, (3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0));
+ point_list.push_back(Vector2(0, 3));
+ point_list.push_back(Vector2(-1.5, (3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0));
+ point_list.push_back(Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0)));
+ point_list.push_back(Vector2(-3, 0));
+ point_list.push_back(Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0)));
+ point_list.push_back(Vector2(-1.5, -(3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0));
+ point_list.push_back(Vector2(0, -3));
+ point_list.push_back(Vector2(1.5, -(3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0));
+ point_list.push_back(Vector2(3, -3.0 * (1.0 - p_overlap * 2.0)));
+
+ Vector2 unit = Vector2(p_size) / 6.0;
+ for (int i = 0; i < point_list.size(); i++) {
+ point_list.write[i] = point_list[i] * unit;
+ }
+
+ PackedVector2Array polygon;
+ if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ polygon.push_back(point_list[0]);
+ polygon.push_back(point_list[1]);
+ polygon.push_back(point_list[2]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER:
+ polygon.push_back(point_list[2]);
+ polygon.push_back(point_list[3]);
+ polygon.push_back(point_list[4]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ polygon.push_back(point_list[4]);
+ polygon.push_back(point_list[5]);
+ polygon.push_back(point_list[6]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ polygon.push_back(point_list[6]);
+ polygon.push_back(point_list[7]);
+ polygon.push_back(point_list[8]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_CORNER:
+ polygon.push_back(point_list[8]);
+ polygon.push_back(point_list[9]);
+ polygon.push_back(point_list[10]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ polygon.push_back(point_list[10]);
+ polygon.push_back(point_list[11]);
+ polygon.push_back(point_list[0]);
+ break;
+ default:
+ break;
+ }
+ } else {
+ if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) {
+ for (int i = 0; i < point_list.size(); i++) {
+ point_list.write[i] = Vector2(point_list[i].y, point_list[i].x);
+ }
+ }
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_CORNER:
+ polygon.push_back(point_list[2]);
+ polygon.push_back(point_list[3]);
+ polygon.push_back(point_list[4]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ polygon.push_back(point_list[0]);
+ polygon.push_back(point_list[1]);
+ polygon.push_back(point_list[2]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ polygon.push_back(point_list[10]);
+ polygon.push_back(point_list[11]);
+ polygon.push_back(point_list[0]);
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_CORNER:
+ polygon.push_back(point_list[8]);
+ polygon.push_back(point_list[9]);
+ polygon.push_back(point_list[10]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ polygon.push_back(point_list[6]);
+ polygon.push_back(point_list[7]);
+ polygon.push_back(point_list[8]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ polygon.push_back(point_list[4]);
+ polygon.push_back(point_list[5]);
+ polygon.push_back(point_list[6]);
+ break;
+ default:
+ break;
+ }
+ }
+
+ int half_polygon_size = polygon.size();
+ for (int i = 0; i < half_polygon_size; i++) {
+ polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0);
+ }
+
+ if (!polygon.is_empty()) {
+ p_canvas_item->draw_polygon(polygon, color_array);
+ }
+}
+
+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) {
+ PackedColorArray color_array;
+ color_array.push_back(p_color);
+
+ PackedVector2Array point_list;
+ point_list.push_back(Vector2(3, 3.0 * (1.0 - p_overlap * 2.0)));
+ point_list.push_back(Vector2(0, 3));
+ point_list.push_back(Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0)));
+ point_list.push_back(Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0)));
+ point_list.push_back(Vector2(0, -3));
+ point_list.push_back(Vector2(3, -3.0 * (1.0 - p_overlap * 2.0)));
+
+ Vector2 unit = Vector2(p_size) / 6.0;
+ for (int i = 0; i < point_list.size(); i++) {
+ point_list.write[i] = point_list[i] * unit;
+ }
+
+ PackedVector2Array polygon;
+ if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_SIDE:
+ polygon.push_back(point_list[5]);
+ polygon.push_back(point_list[0]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ polygon.push_back(point_list[0]);
+ polygon.push_back(point_list[1]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ polygon.push_back(point_list[1]);
+ polygon.push_back(point_list[2]);
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_SIDE:
+ polygon.push_back(point_list[2]);
+ polygon.push_back(point_list[3]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ polygon.push_back(point_list[3]);
+ polygon.push_back(point_list[4]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ polygon.push_back(point_list[4]);
+ polygon.push_back(point_list[5]);
+ break;
+ default:
+ break;
+ }
+ } else {
+ if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) {
+ for (int i = 0; i < point_list.size(); i++) {
+ point_list.write[i] = Vector2(point_list[i].y, point_list[i].x);
+ }
+ }
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ polygon.push_back(point_list[0]);
+ polygon.push_back(point_list[1]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE:
+ polygon.push_back(point_list[5]);
+ polygon.push_back(point_list[0]);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ polygon.push_back(point_list[4]);
+ polygon.push_back(point_list[5]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ polygon.push_back(point_list[3]);
+ polygon.push_back(point_list[4]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_SIDE:
+ polygon.push_back(point_list[2]);
+ polygon.push_back(point_list[3]);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ polygon.push_back(point_list[1]);
+ polygon.push_back(point_list[2]);
+ break;
+ default:
+ break;
+ }
+ }
+
+ int half_polygon_size = polygon.size();
+ for (int i = 0; i < half_polygon_size; i++) {
+ polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0);
+ }
+
+ if (!polygon.is_empty()) {
+ p_canvas_item->draw_polygon(polygon, color_array);
}
- Ref<ConvexPolygonShape2D> convex = p_shape;
- if (!convex.is_valid()) {
+}
+
+#define TERRAIN_ALPHA 0.8
+
+#define DRAW_TERRAIN_BIT(f, bit) \
+ { \
+ int terrain_id = p_tile_data->get_peering_bit_terrain((bit)); \
+ if (terrain_id >= 0) { \
+ Color color = p_tile_set->get_terrain_color(terrain_set, terrain_id); \
+ color.a = TERRAIN_ALPHA; \
+ f(p_canvas_item, color, size, (bit)); \
+ } \
+ }
+
+#define DRAW_HALF_OFFSET_TERRAIN_BIT(f, bit, overlap, half_offset_axis) \
+ { \
+ int terrain_id = p_tile_data->get_peering_bit_terrain((bit)); \
+ if (terrain_id >= 0) { \
+ Color color = p_tile_set->get_terrain_color(terrain_set, terrain_id); \
+ color.a = TERRAIN_ALPHA; \
+ f(p_canvas_item, color, size, (bit), overlap, half_offset_axis); \
+ } \
+ }
+
+void TileSetAtlasPluginTerrain::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);
+
+ int terrain_set = p_tile_data->get_terrain_set();
+ if (terrain_set < 0) {
return;
}
- Vector<Vector<Vector2>> decomp = Geometry2D::decompose_polygon_in_convex(convex->get_points());
- if (decomp.size() > 1) {
- Array sub_shapes;
- for (int i = 0; i < decomp.size(); i++) {
- Ref<ConvexPolygonShape2D> _convex = memnew(ConvexPolygonShape2D);
- _convex->set_points(decomp[i]);
- sub_shapes.append(_convex);
+ TileSet::TerrainMode terrain_mode = p_tile_set->get_terrain_set_mode(terrain_set);
+
+ TileSet::TileShape shape = p_tile_set->get_tile_shape();
+ Vector2i size = p_tile_set->get_tile_size();
+
+ RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform);
+ if (shape == TileSet::TILE_SHAPE_SQUARE) {
+ if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) {
+ DRAW_TERRAIN_BIT(_draw_square_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_RIGHT_SIDE);
+ DRAW_TERRAIN_BIT(_draw_square_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER);
+ DRAW_TERRAIN_BIT(_draw_square_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE);
+ DRAW_TERRAIN_BIT(_draw_square_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER);
+ DRAW_TERRAIN_BIT(_draw_square_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ DRAW_TERRAIN_BIT(_draw_square_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER);
+ DRAW_TERRAIN_BIT(_draw_square_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ DRAW_TERRAIN_BIT(_draw_square_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER);
+ } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) {
+ DRAW_TERRAIN_BIT(_draw_square_corner_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER);
+ DRAW_TERRAIN_BIT(_draw_square_corner_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER);
+ DRAW_TERRAIN_BIT(_draw_square_corner_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER);
+ DRAW_TERRAIN_BIT(_draw_square_corner_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER);
+ } else { // TileData::TERRAIN_MODE_MATCH_SIDES
+ DRAW_TERRAIN_BIT(_draw_square_side_terrain_bit, TileSet::CELL_NEIGHBOR_RIGHT_SIDE);
+ DRAW_TERRAIN_BIT(_draw_square_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE);
+ DRAW_TERRAIN_BIT(_draw_square_side_terrain_bit, TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ DRAW_TERRAIN_BIT(_draw_square_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ }
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) {
+ DRAW_TERRAIN_BIT(_draw_isometric_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_RIGHT_CORNER);
+ DRAW_TERRAIN_BIT(_draw_isometric_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE);
+ DRAW_TERRAIN_BIT(_draw_isometric_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER);
+ DRAW_TERRAIN_BIT(_draw_isometric_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE);
+ DRAW_TERRAIN_BIT(_draw_isometric_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_LEFT_CORNER);
+ DRAW_TERRAIN_BIT(_draw_isometric_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ DRAW_TERRAIN_BIT(_draw_isometric_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_CORNER);
+ DRAW_TERRAIN_BIT(_draw_isometric_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) {
+ DRAW_TERRAIN_BIT(_draw_isometric_corner_terrain_bit, TileSet::CELL_NEIGHBOR_RIGHT_CORNER);
+ DRAW_TERRAIN_BIT(_draw_isometric_corner_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER);
+ DRAW_TERRAIN_BIT(_draw_isometric_corner_terrain_bit, TileSet::CELL_NEIGHBOR_LEFT_CORNER);
+ DRAW_TERRAIN_BIT(_draw_isometric_corner_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_CORNER);
+ } else { // TileData::TERRAIN_MODE_MATCH_SIDES
+ DRAW_TERRAIN_BIT(_draw_isometric_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE);
+ DRAW_TERRAIN_BIT(_draw_isometric_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE);
+ DRAW_TERRAIN_BIT(_draw_isometric_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ DRAW_TERRAIN_BIT(_draw_isometric_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
}
- convex->set_meta("decomposed", sub_shapes);
} else {
- convex->set_meta("decomposed", Variant());
+ TileSet::TileOffsetAxis offset_axis = p_tile_set->get_tile_offset_axis();
+ float overlap = 0.0;
+ switch (p_tile_set->get_tile_shape()) {
+ case TileSet::TILE_SHAPE_HEXAGON:
+ overlap = 0.25;
+ break;
+ case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE:
+ overlap = 0.0;
+ break;
+ default:
+ break;
+ }
+ if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) {
+ if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_RIGHT_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_LEFT_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER, overlap, offset_axis);
+ } else {
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_RIGHT_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_LEFT_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, overlap, offset_axis);
+ }
+ } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) {
+ if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER, overlap, offset_axis);
+ } else {
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_RIGHT_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_LEFT_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER, overlap, offset_axis);
+ }
+ } else { // TileData::TERRAIN_MODE_MATCH_SIDES
+ if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_RIGHT_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_LEFT_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, overlap, offset_axis);
+ } else {
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_SIDE, overlap, offset_axis);
+ DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, overlap, offset_axis);
+ }
+ }
}
+ RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D());
}
-void TileSet::get_tile_list(List<int> *p_tiles) const {
- for (Map<int, TileData>::Element *E = tile_map.front(); E; E = E->next()) {
- p_tiles->push_back(E->key());
+/////////////////////////////// TileSetAtlasPluginRendering //////////////////////////////////////
+
+void TileSetAtlasPluginRendering::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();
+ for (Map<Vector2i, TileMapQuadrant>::Element *E_quadrant = p_tile_map->get_quadrant_map().front(); E_quadrant; E_quadrant = E_quadrant->next()) {
+ TileMapQuadrant &q = E_quadrant->get();
+
+ // Update occluders transform.
+ for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E_cell = q.world_to_map.front(); E_cell; E_cell = E_cell->next()) {
+ Transform2D xform;
+ xform.set_origin(E_cell->key());
+ for (List<RID>::Element *E_occluder_id = q.occluders.front(); E_occluder_id; E_occluder_id = E_occluder_id->next()) {
+ RS::get_singleton()->canvas_light_occluder_set_enabled(E_occluder_id->get(), visible);
+ }
+ }
+ }
+ } break;
+ case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: {
+ if (!p_tile_map->is_inside_tree()) {
+ return;
+ }
+
+ for (Map<Vector2i, TileMapQuadrant>::Element *E_quadrant = p_tile_map->get_quadrant_map().front(); E_quadrant; E_quadrant = E_quadrant->next()) {
+ TileMapQuadrant &q = E_quadrant->get();
+
+ // Update occluders transform.
+ for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E_cell = q.world_to_map.front(); E_cell; E_cell = E_cell->next()) {
+ Transform2D xform;
+ xform.set_origin(E_cell->key());
+ for (List<RID>::Element *E_occluder_id = q.occluders.front(); E_occluder_id; E_occluder_id = E_occluder_id->next()) {
+ RS::get_singleton()->canvas_light_occluder_set_transform(E_occluder_id->get(), p_tile_map->get_global_transform() * xform);
+ }
+ }
+ }
+ } break;
}
}
-bool TileSet::has_tile(int p_id) const {
- return tile_map.has(p_id);
+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) {
+ 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));
+ ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile));
+
+ TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id);
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ // Get the texture.
+ Ref<Texture2D> tex = atlas_source->get_texture();
+ if (!tex.is_valid()) {
+ return;
+ }
+
+ // Get tile data.
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile));
+
+ // Compute the offset
+ Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords);
+ Vector2i tile_offset = atlas_source->get_tile_effective_texture_offset(p_atlas_coords, p_alternative_tile);
+
+ // Compute the destination rectangle in the CanvasItem.
+ Rect2 dest_rect;
+ dest_rect.size = source_rect.size;
+ dest_rect.size.x += fp_adjust;
+ dest_rect.size.y += fp_adjust;
+
+ bool transpose = tile_data->get_transpose();
+ if (transpose) {
+ dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset);
+ } else {
+ dest_rect.position = (p_position - dest_rect.size / 2 - tile_offset);
+ }
+
+ if (tile_data->get_flip_h()) {
+ dest_rect.size.x = -dest_rect.size.x;
+ }
+
+ if (tile_data->get_flip_v()) {
+ dest_rect.size.y = -dest_rect.size.y;
+ }
+
+ // Get the tile modulation.
+ Color modulate = tile_data->get_modulate();
+ modulate = Color(modulate.r * p_modulation.r, modulate.g * p_modulation.g, modulate.b * p_modulation.b, modulate.a * p_modulation.a);
+
+ // Draw the tile.
+ tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping());
+ }
}
-bool TileSet::is_tile_bound(int p_drawn_id, int p_neighbor_id) {
- if (p_drawn_id == p_neighbor_id) {
- return true;
- } else if (get_script_instance() != nullptr) {
- if (get_script_instance()->has_method("_is_tile_bound")) {
- Variant ret = get_script_instance()->call("_is_tile_bound", p_drawn_id, p_neighbor_id);
- if (ret.get_type() == Variant::BOOL) {
- return ret;
+void TileSetAtlasPluginRendering::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();
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ bool visible = p_tile_map->is_visible_in_tree();
+
+ SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first();
+ while (q_list_element) {
+ TileMapQuadrant &q = *q_list_element->self();
+
+ RenderingServer *rs = RenderingServer::get_singleton();
+
+ // Free the canvas items.
+ for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) {
+ rs->free(E->get());
+ }
+ q.canvas_items.clear();
+
+ // Free the occluders.
+ for (List<RID>::Element *E = q.occluders.front(); E; E = E->next()) {
+ rs->free(E->get());
+ }
+ q.occluders.clear();
+
+ // Those allow to group cell per material or z-index.
+ Ref<ShaderMaterial> prev_material;
+ int prev_z_index = 0;
+ RID prev_canvas_item;
+
+ // Iterate over the cells of the quadrant.
+ for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E_cell = q.world_to_map.front(); E_cell; E_cell = E_cell->next()) {
+ TileMapCell c = p_tile_map->get_cell(E_cell->value());
+
+ 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) {
+ // Get the tile data.
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile));
+ Ref<ShaderMaterial> mat = tile_data->tile_get_material();
+ int z_index = tile_data->get_z_index();
+
+ // Quandrant pos.
+ Vector2 position = p_tile_map->map_to_world(q.coords * p_tile_map->get_effective_quadrant_size()) - tile_set->get_tile_size() / 2;
+
+ // --- CanvasItems ---
+ // Create two canvas items, for rendering and debug.
+ RID canvas_item;
+
+ // Check if the material or the z_index changed.
+ if (prev_canvas_item == RID() || prev_material != mat || prev_z_index != z_index) {
+ canvas_item = rs->canvas_item_create();
+ if (mat.is_valid()) {
+ rs->canvas_item_set_material(canvas_item, mat->get_rid());
+ }
+ rs->canvas_item_set_parent(canvas_item, p_tile_map->get_canvas_item());
+ rs->canvas_item_set_use_parent_material(canvas_item, p_tile_map->get_use_parent_material() || p_tile_map->get_material().is_valid());
+ Transform2D xform;
+ xform.set_origin(position);
+
+ rs->canvas_item_set_transform(canvas_item, xform);
+ rs->canvas_item_set_light_mask(canvas_item, p_tile_map->get_light_mask());
+ rs->canvas_item_set_z_index(canvas_item, z_index);
+
+ rs->canvas_item_set_default_texture_filter(canvas_item, RS::CanvasItemTextureFilter(p_tile_map->CanvasItem::get_texture_filter()));
+ rs->canvas_item_set_default_texture_repeat(canvas_item, RS::CanvasItemTextureRepeat(p_tile_map->CanvasItem::get_texture_repeat()));
+
+ q.canvas_items.push_back(canvas_item);
+
+ prev_canvas_item = canvas_item;
+ prev_material = mat;
+ prev_z_index = z_index;
+
+ } else {
+ // Keep the same canvas_item to draw on.
+ canvas_item = prev_canvas_item;
+ }
+
+ // Drawing the tile in the canvas item.
+ draw_tile(canvas_item, E_cell->key() - position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, p_tile_map->get_self_modulate());
+
+ // --- Occluders ---
+ for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) {
+ Transform2D xform;
+ xform.set_origin(E_cell->key());
+ if (tile_data->get_occluder(i).is_valid()) {
+ RID occluder_id = rs->canvas_light_occluder_create();
+ rs->canvas_light_occluder_set_enabled(occluder_id, visible);
+ rs->canvas_light_occluder_set_transform(occluder_id, p_tile_map->get_global_transform() * xform);
+ rs->canvas_light_occluder_set_polygon(occluder_id, tile_data->get_occluder(i)->get_rid());
+ rs->canvas_light_occluder_attach_to_canvas(occluder_id, p_tile_map->get_canvas());
+ rs->canvas_light_occluder_set_light_mask(occluder_id, tile_set->get_occlusion_layer_light_mask(i));
+ q.occluders.push_back(occluder_id);
+ }
+ }
+ }
}
}
+
+ quadrant_order_dirty = true;
+ q_list_element = q_list_element->next();
+ }
+
+ // Reset the drawing indices
+ if (quadrant_order_dirty) {
+ int index = -(int64_t)0x80000000; //always must be drawn below children.
+
+ // Sort the quadrants coords per world coordinates
+ Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator> world_to_map;
+ Map<Vector2i, TileMapQuadrant> quadrant_map = p_tile_map->get_quadrant_map();
+ for (Map<Vector2i, TileMapQuadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
+ world_to_map[p_tile_map->map_to_world(E->key())] = E->key();
+ }
+
+ // Sort the quadrants
+ for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E = world_to_map.front(); E; E = E->next()) {
+ TileMapQuadrant &q = quadrant_map[E->value()];
+ for (List<RID>::Element *F = q.canvas_items.front(); F; F = F->next()) {
+ RS::get_singleton()->canvas_item_set_draw_index(F->get(), index++);
+ }
+ }
+
+ quadrant_order_dirty = false;
}
- return false;
}
-void TileSet::remove_tile(int p_id) {
- ERR_FAIL_COND(!tile_map.has(p_id));
- tile_map.erase(p_id);
- notify_property_list_changed();
- emit_changed();
+void TileSetAtlasPluginRendering::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;
}
-int TileSet::get_last_unused_tile_id() const {
- if (tile_map.size()) {
- return tile_map.back()->key() + 1;
- } else {
- return 0;
+void TileSetAtlasPluginRendering::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());
+ }
+ p_quadrant->canvas_items.clear();
+
+ // Free the occluders.
+ for (List<RID>::Element *E = p_quadrant->occluders.front(); E; E = E->next()) {
+ RenderingServer::get_singleton()->free(E->get());
}
+ p_quadrant->occluders.clear();
}
-int TileSet::find_tile_by_name(const String &p_name) const {
- for (Map<int, TileData>::Element *E = tile_map.front(); E; E = E->next()) {
- if (p_name == E->get().name) {
- return E->key();
+/////////////////////////////// TileSetAtlasPluginPhysics //////////////////////////////////////
+
+void TileSetAtlasPluginPhysics::tilemap_notification(TileMap *p_tile_map, int p_what) {
+ switch (p_what) {
+ case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: {
+ // Update the bodies transforms.
+ if (p_tile_map->is_inside_tree()) {
+ Map<Vector2i, TileMapQuadrant> quadrant_map = p_tile_map->get_quadrant_map();
+ Transform2D global_transform = p_tile_map->get_global_transform();
+
+ for (Map<Vector2i, TileMapQuadrant>::Element *E = quadrant_map.front(); E; E = E->next()) {
+ TileMapQuadrant &q = E->get();
+
+ Transform2D xform;
+ xform.set_origin(p_tile_map->map_to_world(E->key() * p_tile_map->get_effective_quadrant_size()));
+ xform = global_transform * xform;
+
+ for (int body_index = 0; body_index < q.bodies.size(); body_index++) {
+ PhysicsServer2D::get_singleton()->body_set_state(q.bodies[body_index], PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
+ }
+ }
+ }
+ } break;
+ }
+}
+
+void TileSetAtlasPluginPhysics::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();
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ Transform2D global_transform = p_tile_map->get_global_transform();
+ PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
+
+ SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first();
+ while (q_list_element) {
+ TileMapQuadrant &q = *q_list_element->self();
+
+ Vector2 quadrant_pos = p_tile_map->map_to_world(q.coords * p_tile_map->get_effective_quadrant_size());
+
+ // Clear shapes.
+ for (int body_index = 0; body_index < q.bodies.size(); body_index++) {
+ ps->body_clear_shapes(q.bodies[body_index]);
+
+ // Position the bodies.
+ Transform2D xform;
+ xform.set_origin(quadrant_pos);
+ xform = global_transform * xform;
+ ps->body_set_state(q.bodies[body_index], PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
}
+
+ for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) {
+ 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;
+ }
+
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile));
+
+ for (int body_index = 0; body_index < q.bodies.size(); body_index++) {
+ // Add the shapes again.
+ for (int shape_index = 0; shape_index < tile_data->get_collision_shapes_count(body_index); shape_index++) {
+ bool one_way_collision = tile_data->is_collision_shape_one_way(body_index, shape_index);
+ float one_way_collision_margin = tile_data->get_collision_shape_one_way_margin(body_index, shape_index);
+ Ref<Shape2D> shape = tile_data->get_collision_shape_shape(body_index, shape_index);
+ if (shape.is_valid()) {
+ Transform2D xform = Transform2D();
+ xform.set_origin(p_tile_map->map_to_world(E_cell->get()) - quadrant_pos);
+
+ // Add decomposed convex shapes.
+ ps->body_add_shape(q.bodies[body_index], shape->get_rid(), xform);
+ ps->body_set_shape_metadata(q.bodies[body_index], shape_index, E_cell->get());
+ ps->body_set_shape_as_one_way_collision(q.bodies[body_index], shape_index, one_way_collision, one_way_collision_margin);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ q_list_element = q_list_element->next();
}
- return -1;
}
-void TileSet::reset_state() {
- clear();
+void TileSetAtlasPluginPhysics::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());
+
+ //Get the TileMap's gobla transform.
+ Transform2D global_transform;
+ if (p_tile_map->is_inside_tree()) {
+ global_transform = p_tile_map->get_global_transform();
+ }
+
+ // Clear all bodies.
+ p_quadrant->bodies.clear();
+
+ // Create the body and set its parameters.
+ for (int layer_index = 0; layer_index < tile_set->get_physics_layers_count(); layer_index++) {
+ RID body = PhysicsServer2D::get_singleton()->body_create();
+ PhysicsServer2D::get_singleton()->body_set_mode(body, PhysicsServer2D::BODY_MODE_STATIC);
+
+ PhysicsServer2D::get_singleton()->body_attach_object_instance_id(body, p_tile_map->get_instance_id());
+ PhysicsServer2D::get_singleton()->body_set_collision_layer(body, tile_set->get_physics_layer_collision_layer(layer_index));
+ PhysicsServer2D::get_singleton()->body_set_collision_mask(body, tile_set->get_physics_layer_collision_mask(layer_index));
+
+ Ref<PhysicsMaterial> physics_material = tile_set->get_physics_layer_physics_material(layer_index);
+ if (!physics_material.is_valid()) {
+ PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, 0);
+ PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, 1);
+ } else {
+ PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material->computed_bounce());
+ PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, physics_material->computed_friction());
+ }
+
+ if (p_tile_map->is_inside_tree()) {
+ RID space = p_tile_map->get_world_2d()->get_space();
+ PhysicsServer2D::get_singleton()->body_set_space(body, space);
+
+ Transform2D xform;
+ xform.set_origin(p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size()));
+ xform = global_transform * xform;
+ PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
+ }
+
+ p_quadrant->bodies.push_back(body);
+ }
}
-void TileSet::clear() {
- tile_map.clear();
- notify_property_list_changed();
- emit_changed();
+void TileSetAtlasPluginPhysics::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]);
+ }
+ p_quadrant->bodies.clear();
}
-void TileSet::_bind_methods() {
- ClassDB::bind_method(D_METHOD("create_tile", "id"), &TileSet::create_tile);
- ClassDB::bind_method(D_METHOD("autotile_clear_bitmask_map", "id"), &TileSet::autotile_clear_bitmask_map);
- ClassDB::bind_method(D_METHOD("autotile_set_icon_coordinate", "id", "coord"), &TileSet::autotile_set_icon_coordinate);
- ClassDB::bind_method(D_METHOD("autotile_get_icon_coordinate", "id"), &TileSet::autotile_get_icon_coordinate);
- ClassDB::bind_method(D_METHOD("autotile_set_subtile_priority", "id", "coord", "priority"), &TileSet::autotile_set_subtile_priority);
- ClassDB::bind_method(D_METHOD("autotile_get_subtile_priority", "id", "coord"), &TileSet::autotile_get_subtile_priority);
- ClassDB::bind_method(D_METHOD("autotile_set_z_index", "id", "coord", "z_index"), &TileSet::autotile_set_z_index);
- ClassDB::bind_method(D_METHOD("autotile_get_z_index", "id", "coord"), &TileSet::autotile_get_z_index);
- ClassDB::bind_method(D_METHOD("autotile_set_light_occluder", "id", "light_occluder", "coord"), &TileSet::autotile_set_light_occluder);
- ClassDB::bind_method(D_METHOD("autotile_get_light_occluder", "id", "coord"), &TileSet::autotile_get_light_occluder);
- ClassDB::bind_method(D_METHOD("autotile_set_navigation_polygon", "id", "navigation_polygon", "coord"), &TileSet::autotile_set_navigation_polygon);
- ClassDB::bind_method(D_METHOD("autotile_get_navigation_polygon", "id", "coord"), &TileSet::autotile_get_navigation_polygon);
- ClassDB::bind_method(D_METHOD("autotile_set_bitmask", "id", "bitmask", "flag"), &TileSet::autotile_set_bitmask);
- ClassDB::bind_method(D_METHOD("autotile_get_bitmask", "id", "coord"), &TileSet::autotile_get_bitmask);
- ClassDB::bind_method(D_METHOD("autotile_set_bitmask_mode", "id", "mode"), &TileSet::autotile_set_bitmask_mode);
- ClassDB::bind_method(D_METHOD("autotile_get_bitmask_mode", "id"), &TileSet::autotile_get_bitmask_mode);
- ClassDB::bind_method(D_METHOD("autotile_set_spacing", "id", "spacing"), &TileSet::autotile_set_spacing);
- ClassDB::bind_method(D_METHOD("autotile_get_spacing", "id"), &TileSet::autotile_get_spacing);
- ClassDB::bind_method(D_METHOD("autotile_set_size", "id", "size"), &TileSet::autotile_set_size);
- ClassDB::bind_method(D_METHOD("autotile_get_size", "id"), &TileSet::autotile_get_size);
- ClassDB::bind_method(D_METHOD("tile_set_name", "id", "name"), &TileSet::tile_set_name);
- ClassDB::bind_method(D_METHOD("tile_get_name", "id"), &TileSet::tile_get_name);
- ClassDB::bind_method(D_METHOD("tile_set_texture", "id", "texture"), &TileSet::tile_set_texture);
- ClassDB::bind_method(D_METHOD("tile_get_texture", "id"), &TileSet::tile_get_texture);
- ClassDB::bind_method(D_METHOD("tile_set_material", "id", "material"), &TileSet::tile_set_material);
- ClassDB::bind_method(D_METHOD("tile_get_material", "id"), &TileSet::tile_get_material);
- ClassDB::bind_method(D_METHOD("tile_set_modulate", "id", "color"), &TileSet::tile_set_modulate);
- ClassDB::bind_method(D_METHOD("tile_get_modulate", "id"), &TileSet::tile_get_modulate);
- ClassDB::bind_method(D_METHOD("tile_set_texture_offset", "id", "texture_offset"), &TileSet::tile_set_texture_offset);
- ClassDB::bind_method(D_METHOD("tile_get_texture_offset", "id"), &TileSet::tile_get_texture_offset);
- ClassDB::bind_method(D_METHOD("tile_set_region", "id", "region"), &TileSet::tile_set_region);
- ClassDB::bind_method(D_METHOD("tile_get_region", "id"), &TileSet::tile_get_region);
- ClassDB::bind_method(D_METHOD("tile_set_shape", "id", "shape_id", "shape"), &TileSet::tile_set_shape);
- ClassDB::bind_method(D_METHOD("tile_get_shape", "id", "shape_id"), &TileSet::tile_get_shape);
- ClassDB::bind_method(D_METHOD("tile_set_shape_offset", "id", "shape_id", "shape_offset"), &TileSet::tile_set_shape_offset);
- ClassDB::bind_method(D_METHOD("tile_get_shape_offset", "id", "shape_id"), &TileSet::tile_get_shape_offset);
- ClassDB::bind_method(D_METHOD("tile_set_shape_transform", "id", "shape_id", "shape_transform"), &TileSet::tile_set_shape_transform);
- ClassDB::bind_method(D_METHOD("tile_get_shape_transform", "id", "shape_id"), &TileSet::tile_get_shape_transform);
- ClassDB::bind_method(D_METHOD("tile_set_shape_one_way", "id", "shape_id", "one_way"), &TileSet::tile_set_shape_one_way);
- ClassDB::bind_method(D_METHOD("tile_get_shape_one_way", "id", "shape_id"), &TileSet::tile_get_shape_one_way);
- ClassDB::bind_method(D_METHOD("tile_set_shape_one_way_margin", "id", "shape_id", "one_way"), &TileSet::tile_set_shape_one_way_margin);
- ClassDB::bind_method(D_METHOD("tile_get_shape_one_way_margin", "id", "shape_id"), &TileSet::tile_get_shape_one_way_margin);
- ClassDB::bind_method(D_METHOD("tile_add_shape", "id", "shape", "shape_transform", "one_way", "autotile_coord"), &TileSet::tile_add_shape, DEFVAL(false), DEFVAL(Vector2()));
- ClassDB::bind_method(D_METHOD("tile_get_shape_count", "id"), &TileSet::tile_get_shape_count);
- ClassDB::bind_method(D_METHOD("tile_set_shapes", "id", "shapes"), &TileSet::_tile_set_shapes);
- ClassDB::bind_method(D_METHOD("tile_get_shapes", "id"), &TileSet::_tile_get_shapes);
- ClassDB::bind_method(D_METHOD("tile_set_tile_mode", "id", "tilemode"), &TileSet::tile_set_tile_mode);
- ClassDB::bind_method(D_METHOD("tile_get_tile_mode", "id"), &TileSet::tile_get_tile_mode);
- ClassDB::bind_method(D_METHOD("tile_set_navigation_polygon", "id", "navigation_polygon"), &TileSet::tile_set_navigation_polygon);
- ClassDB::bind_method(D_METHOD("tile_get_navigation_polygon", "id"), &TileSet::tile_get_navigation_polygon);
- ClassDB::bind_method(D_METHOD("tile_set_navigation_polygon_offset", "id", "navigation_polygon_offset"), &TileSet::tile_set_navigation_polygon_offset);
- ClassDB::bind_method(D_METHOD("tile_get_navigation_polygon_offset", "id"), &TileSet::tile_get_navigation_polygon_offset);
- ClassDB::bind_method(D_METHOD("tile_set_light_occluder", "id", "light_occluder"), &TileSet::tile_set_light_occluder);
- ClassDB::bind_method(D_METHOD("tile_get_light_occluder", "id"), &TileSet::tile_get_light_occluder);
- ClassDB::bind_method(D_METHOD("tile_set_occluder_offset", "id", "occluder_offset"), &TileSet::tile_set_occluder_offset);
- ClassDB::bind_method(D_METHOD("tile_get_occluder_offset", "id"), &TileSet::tile_get_occluder_offset);
- ClassDB::bind_method(D_METHOD("tile_set_z_index", "id", "z_index"), &TileSet::tile_set_z_index);
- ClassDB::bind_method(D_METHOD("tile_get_z_index", "id"), &TileSet::tile_get_z_index);
-
- ClassDB::bind_method(D_METHOD("remove_tile", "id"), &TileSet::remove_tile);
- ClassDB::bind_method(D_METHOD("clear"), &TileSet::clear);
- ClassDB::bind_method(D_METHOD("get_last_unused_tile_id"), &TileSet::get_last_unused_tile_id);
- ClassDB::bind_method(D_METHOD("find_tile_by_name", "name"), &TileSet::find_tile_by_name);
- ClassDB::bind_method(D_METHOD("get_tiles_ids"), &TileSet::_get_tiles_ids);
-
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_tile_bound", PropertyInfo(Variant::INT, "drawn_id"), PropertyInfo(Variant::INT, "neighbor_id")));
- BIND_VMETHOD(MethodInfo(Variant::VECTOR2, "_forward_subtile_selection", PropertyInfo(Variant::INT, "autotile_id"), PropertyInfo(Variant::INT, "bitmask"), PropertyInfo(Variant::OBJECT, "tilemap", PROPERTY_HINT_NONE, "TileMap"), PropertyInfo(Variant::VECTOR2, "tile_location")));
- BIND_VMETHOD(MethodInfo(Variant::VECTOR2, "_forward_atlas_subtile_selection", PropertyInfo(Variant::INT, "atlastile_id"), PropertyInfo(Variant::OBJECT, "tilemap", PROPERTY_HINT_NONE, "TileMap"), PropertyInfo(Variant::VECTOR2, "tile_location")));
-
- BIND_ENUM_CONSTANT(BITMASK_2X2);
- BIND_ENUM_CONSTANT(BITMASK_3X3_MINIMAL);
- BIND_ENUM_CONSTANT(BITMASK_3X3);
-
- BIND_ENUM_CONSTANT(BIND_TOPLEFT);
- BIND_ENUM_CONSTANT(BIND_TOP);
- BIND_ENUM_CONSTANT(BIND_TOPRIGHT);
- BIND_ENUM_CONSTANT(BIND_LEFT);
- BIND_ENUM_CONSTANT(BIND_CENTER);
- BIND_ENUM_CONSTANT(BIND_RIGHT);
- BIND_ENUM_CONSTANT(BIND_BOTTOMLEFT);
- BIND_ENUM_CONSTANT(BIND_BOTTOM);
- BIND_ENUM_CONSTANT(BIND_BOTTOMRIGHT);
-
- BIND_ENUM_CONSTANT(SINGLE_TILE);
- BIND_ENUM_CONSTANT(AUTO_TILE);
- BIND_ENUM_CONSTANT(ATLAS_TILE);
+void TileSetAtlasPluginPhysics::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());
+
+ if (!p_tile_map->get_tree() || !(Engine::get_singleton()->is_editor_hint() || p_tile_map->get_tree()->is_debugging_collisions_hint())) {
+ return;
+ }
+
+ RenderingServer *rs = RenderingServer::get_singleton();
+
+ Vector2 quadrant_pos = p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size());
+
+ Color debug_collision_color = p_tile_map->get_tree()->get_debug_collisions_color();
+ for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) {
+ TileMapCell c = p_tile_map->get_cell(E_cell->get());
+
+ 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);
+
+ if (tile_set->has_source(c.source_id)) {
+ TileSetSource *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) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile));
+
+ for (int body_index = 0; body_index < p_quadrant->bodies.size(); body_index++) {
+ for (int shape_index = 0; shape_index < tile_data->get_collision_shapes_count(body_index); shape_index++) {
+ // Draw the debug shape.
+ Ref<Shape2D> shape = tile_data->get_collision_shape_shape(body_index, shape_index);
+ if (shape.is_valid()) {
+ shape->draw(p_quadrant->debug_canvas_item, debug_collision_color);
+ }
+ }
+ }
+ }
+ }
+ rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, Transform2D());
+ }
+};
+
+/////////////////////////////// TileSetAtlasPluginNavigation //////////////////////////////////////
+
+void TileSetAtlasPluginNavigation::tilemap_notification(TileMap *p_tile_map, int p_what) {
+ switch (p_what) {
+ case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: {
+ if (p_tile_map->is_inside_tree()) {
+ Map<Vector2i, TileMapQuadrant> quadrant_map = p_tile_map->get_quadrant_map();
+ Transform2D tilemap_xform = p_tile_map->get_global_transform();
+ for (Map<Vector2i, TileMapQuadrant>::Element *E_quadrant = quadrant_map.front(); E_quadrant; E_quadrant = E_quadrant->next()) {
+ TileMapQuadrant &q = E_quadrant->get();
+ for (Map<Vector2i, Vector<RID>>::Element *E_region = q.navigation_regions.front(); E_region; E_region = E_region->next()) {
+ for (int layer_index = 0; layer_index < E_region->get().size(); layer_index++) {
+ RID region = E_region->get()[layer_index];
+ if (!region.is_valid()) {
+ continue;
+ }
+ Transform2D tile_transform;
+ tile_transform.set_origin(p_tile_map->map_to_world(E_region->key()));
+ NavigationServer2D::get_singleton()->region_set_transform(region, tilemap_xform * tile_transform);
+ }
+ }
+ }
+ }
+ } break;
+ }
}
-TileSet::TileSet() {
+void TileSetAtlasPluginNavigation::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();
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ // Get colors for debug.
+ SceneTree *st = SceneTree::get_singleton();
+ Color debug_navigation_color;
+ bool debug_navigation = st && st->is_debugging_navigation_hint();
+ if (debug_navigation) {
+ debug_navigation_color = st->get_debug_navigation_color();
+ }
+
+ Transform2D tilemap_xform = p_tile_map->get_global_transform();
+ SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first();
+ while (q_list_element) {
+ TileMapQuadrant &q = *q_list_element->self();
+
+ // Clear navigation shapes in the quadrant.
+ for (Map<Vector2i, Vector<RID>>::Element *E = q.navigation_regions.front(); E; E = E->next()) {
+ for (int i = 0; i < E->get().size(); i++) {
+ RID region = E->get()[i];
+ if (!region.is_valid()) {
+ continue;
+ }
+ NavigationServer2D::get_singleton()->region_set_map(region, RID());
+ }
+ }
+ q.navigation_regions.clear();
+
+ // Get the navigation polygons and create regions.
+ for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) {
+ 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;
+ }
+
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile));
+ q.navigation_regions[E_cell->get()].resize(tile_set->get_navigation_layers_count());
+
+ for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) {
+ Ref<NavigationPolygon> navpoly;
+ navpoly = tile_data->get_navigation_polygon(layer_index);
+
+ if (navpoly.is_valid()) {
+ Transform2D tile_transform;
+ tile_transform.set_origin(p_tile_map->map_to_world(E_cell->get()));
+
+ RID region = NavigationServer2D::get_singleton()->region_create();
+ NavigationServer2D::get_singleton()->region_set_map(region, p_tile_map->get_world_2d()->get_navigation_map());
+ NavigationServer2D::get_singleton()->region_set_transform(region, tilemap_xform * tile_transform);
+ NavigationServer2D::get_singleton()->region_set_navpoly(region, navpoly);
+ q.navigation_regions[E_cell->get()].write[layer_index] = region;
+ }
+ }
+ }
+ }
+ }
+
+ q_list_element = q_list_element->next();
+ }
+}
+
+void TileSetAtlasPluginNavigation::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++) {
+ RID region = E->get()[i];
+ if (!region.is_valid()) {
+ continue;
+ }
+ NavigationServer2D::get_singleton()->free(region);
+ }
+ }
+ p_quadrant->navigation_regions.clear();
+}
+
+void TileSetAtlasPluginNavigation::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());
+
+ if (!p_tile_map->get_tree() || !(Engine::get_singleton()->is_editor_hint() || p_tile_map->get_tree()->is_debugging_navigation_hint())) {
+ return;
+ }
+
+ RenderingServer *rs = RenderingServer::get_singleton();
+
+ Color color = p_tile_map->get_tree()->get_debug_navigation_color();
+ RandomPCG rand;
+
+ 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()) {
+ 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;
+ }
+
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_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);
+
+ for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) {
+ Ref<NavigationPolygon> navpoly = tile_data->get_navigation_polygon(layer_index);
+ if (navpoly.is_valid()) {
+ PackedVector2Array navigation_polygon_vertices = navpoly->get_vertices();
+
+ for (int i = 0; i < navpoly->get_polygon_count(); i++) {
+ // An array of vertices for this polygon.
+ Vector<int> polygon = navpoly->get_polygon(i);
+ Vector<Vector2> vertices;
+ vertices.resize(polygon.size());
+ for (int j = 0; j < polygon.size(); j++) {
+ ERR_FAIL_INDEX(polygon[j], navigation_polygon_vertices.size());
+ vertices.write[j] = navigation_polygon_vertices[polygon[j]];
+ }
+
+ // Generate the polygon color, slightly randomly modified from the settings one.
+ Color random_variation_color;
+ random_variation_color.set_hsv(color.get_h() + rand.random(-1.0, 1.0) * 0.05, color.get_s(), color.get_v() + rand.random(-1.0, 1.0) * 0.1);
+ random_variation_color.a = color.a;
+ Vector<Color> colors;
+ colors.push_back(random_variation_color);
+
+ RS::get_singleton()->canvas_item_add_polygon(p_quadrant->debug_canvas_item, vertices, colors);
+ }
+ }
+ }
+ }
+ }
+ }
}
diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h
index 0a8721f35b..20cf183a20 100644
--- a/scene/resources/tile_set.h
+++ b/scene/resources/tile_set.h
@@ -32,226 +32,614 @@
#define TILE_SET_H
#include "core/io/resource.h"
-#include "core/variant/array.h"
+#include "core/object/object.h"
#include "scene/2d/light_occluder_2d.h"
#include "scene/2d/navigation_region_2d.h"
+#include "scene/main/canvas_item.h"
#include "scene/resources/convex_polygon_shape_2d.h"
+#include "scene/resources/packed_scene.h"
+#include "scene/resources/physics_material.h"
+#include "scene/resources/shape_2d.h"
+
+#ifndef DISABLE_DEPRECATED
+#include "scene/2d/light_occluder_2d.h"
+#include "scene/2d/navigation_region_2d.h"
+#include "scene/resources/shader.h"
#include "scene/resources/shape_2d.h"
#include "scene/resources/texture.h"
+#endif
+
+class TileMap;
+struct TileMapQuadrant;
+class TileSetSource;
+class TileSetAtlasSource;
+class TileData;
+
+// Forward-declare the plugins.
+class TileSetPlugin;
+class TileSetAtlasPluginRendering;
+class TileSetAtlasPluginPhysics;
+class TileSetAtlasPluginNavigation;
+class TileSetAtlasPluginTerrain;
class TileSet : public Resource {
GDCLASS(TileSet, Resource);
-public:
- struct ShapeData {
+#ifndef DISABLE_DEPRECATED
+private:
+ struct CompatibilityShapeData {
+ Vector2i autotile_coords;
+ bool one_way;
+ float one_way_margin;
Ref<Shape2D> shape;
- Transform2D shape_transform;
- Vector2 autotile_coord;
- bool one_way_collision = false;
- float one_way_collision_margin = 1.0;
+ Transform2D transform;
+ };
- ShapeData() {}
+ struct CompatibilityTileData {
+ String name;
+ Ref<Texture2D> texture;
+ Vector2 tex_offset;
+ Ref<ShaderMaterial> material;
+ Rect2 region;
+ int tile_mode;
+ Color modulate;
+
+ // Atlas or autotiles data
+ int autotile_bitmask_mode;
+ Vector2 autotile_icon_coordinate;
+ Size2i autotile_tile_size = Size2i(16, 16);
+
+ int autotile_spacing;
+ Map<Vector2i, int> autotile_bitmask_flags;
+ Map<Vector2i, Ref<OccluderPolygon2D>> autotile_occluder_map;
+ Map<Vector2i, Ref<NavigationPolygon>> autotile_navpoly_map;
+ Map<Vector2i, int> autotile_priority_map;
+ Map<Vector2i, int> autotile_z_index_map;
+
+ Vector<CompatibilityShapeData> shapes;
+ Ref<OccluderPolygon2D> occluder;
+ Vector2 occluder_offset;
+ Ref<NavigationPolygon> navigation;
+ Vector2 navigation_offset;
+ int z_index;
};
- enum BitmaskMode {
- BITMASK_2X2,
- BITMASK_3X3_MINIMAL,
- BITMASK_3X3
+ Map<int, CompatibilityTileData *> compatibility_data = Map<int, CompatibilityTileData *>();
+ Map<int, int> compatibility_source_mapping = Map<int, int>();
+
+private:
+ void compatibility_conversion();
+
+public:
+ int compatibility_get_source_for_tile_id(int p_old_source) {
+ return compatibility_source_mapping[p_old_source];
};
- enum AutotileBindings {
- BIND_TOPLEFT = 1,
- BIND_TOP = 2,
- BIND_TOPRIGHT = 4,
- BIND_LEFT = 8,
- BIND_CENTER = 16,
- BIND_RIGHT = 32,
- BIND_BOTTOMLEFT = 64,
- BIND_BOTTOM = 128,
- BIND_BOTTOMRIGHT = 256,
-
- BIND_IGNORE_TOPLEFT = 1 << 16,
- BIND_IGNORE_TOP = 1 << 17,
- BIND_IGNORE_TOPRIGHT = 1 << 18,
- BIND_IGNORE_LEFT = 1 << 19,
- BIND_IGNORE_CENTER = 1 << 20,
- BIND_IGNORE_RIGHT = 1 << 21,
- BIND_IGNORE_BOTTOMLEFT = 1 << 22,
- BIND_IGNORE_BOTTOM = 1 << 23,
- BIND_IGNORE_BOTTOMRIGHT = 1 << 24
+#endif // DISABLE_DEPRECATED
+
+public:
+ enum CellNeighbor {
+ CELL_NEIGHBOR_RIGHT_SIDE = 0,
+ CELL_NEIGHBOR_RIGHT_CORNER,
+ CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE,
+ CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER,
+ CELL_NEIGHBOR_BOTTOM_SIDE,
+ CELL_NEIGHBOR_BOTTOM_CORNER,
+ CELL_NEIGHBOR_BOTTOM_LEFT_SIDE,
+ CELL_NEIGHBOR_BOTTOM_LEFT_CORNER,
+ CELL_NEIGHBOR_LEFT_SIDE,
+ CELL_NEIGHBOR_LEFT_CORNER,
+ CELL_NEIGHBOR_TOP_LEFT_SIDE,
+ CELL_NEIGHBOR_TOP_LEFT_CORNER,
+ CELL_NEIGHBOR_TOP_SIDE,
+ CELL_NEIGHBOR_TOP_CORNER,
+ CELL_NEIGHBOR_TOP_RIGHT_SIDE,
+ CELL_NEIGHBOR_TOP_RIGHT_CORNER,
+ CELL_NEIGHBOR_MAX,
};
- enum TileMode {
- SINGLE_TILE,
- AUTO_TILE,
- ATLAS_TILE
+ enum TerrainMode {
+ TERRAIN_MODE_MATCH_CORNERS_AND_SIDES = 0,
+ TERRAIN_MODE_MATCH_CORNERS,
+ TERRAIN_MODE_MATCH_SIDES,
};
- struct AutotileData {
- BitmaskMode bitmask_mode = BITMASK_2X2;
- // Default size to prevent invalid value
- Size2 size = Size2(64, 64);
- Vector2 icon_coord = Vector2(0, 0);
- int spacing = 0;
- Map<Vector2, uint32_t> flags;
- Map<Vector2, Ref<OccluderPolygon2D>> occluder_map;
- Map<Vector2, Ref<NavigationPolygon>> navpoly_map;
- Map<Vector2, int> priority_map;
- Map<Vector2, int> z_index_map;
-
- explicit AutotileData() {}
+ enum TileShape {
+ TILE_SHAPE_SQUARE,
+ TILE_SHAPE_ISOMETRIC,
+ TILE_SHAPE_HALF_OFFSET_SQUARE,
+ TILE_SHAPE_HEXAGON,
};
-private:
- struct TileData {
- String name;
- Ref<Texture2D> texture;
- Vector2 offset;
- Rect2i region;
- Vector<ShapeData> shapes_data;
- Vector2 occluder_offset;
- Ref<OccluderPolygon2D> occluder;
- Vector2 navigation_polygon_offset;
- Ref<NavigationPolygon> navigation_polygon;
- Ref<ShaderMaterial> material;
- TileMode tile_mode = SINGLE_TILE;
- // Default modulate for back-compat
- Color modulate = Color(1, 1, 1);
- AutotileData autotile_data;
- int z_index = 0;
+ enum TileLayout {
+ TILE_LAYOUT_STACKED,
+ TILE_LAYOUT_STACKED_OFFSET,
+ TILE_LAYOUT_STAIRS_RIGHT,
+ TILE_LAYOUT_STAIRS_DOWN,
+ TILE_LAYOUT_DIAMOND_RIGHT,
+ TILE_LAYOUT_DIAMOND_DOWN,
+ };
- explicit TileData() {}
+ enum TileOffsetAxis {
+ TILE_OFFSET_AXIS_HORIZONTAL,
+ TILE_OFFSET_AXIS_VERTICAL,
};
- Map<int, TileData> tile_map;
+public:
+ struct PackedSceneSource {
+ Ref<PackedScene> scene;
+ Vector2 offset;
+ };
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;
- void _tile_set_shapes(int p_id, const Array &p_shapes);
- Array _tile_get_shapes(int p_id) const;
- Array _get_tiles_ids() const;
- void _decompose_convex_shape(Ref<Shape2D> p_shape);
+private:
+ // --- TileSet data ---
+ // Basic shape and layout.
+ TileShape tile_shape = TILE_SHAPE_SQUARE;
+ TileLayout tile_layout = TILE_LAYOUT_STACKED;
+ TileOffsetAxis tile_offset_axis = TILE_OFFSET_AXIS_HORIZONTAL;
+ Size2i tile_size = Size2i(16, 16); //Size2(64, 64);
+ Vector2 tile_skew = Vector2(0, 0);
+
+ // Rendering.
+ bool y_sorting = false;
+ bool uv_clipping = false;
+ struct OcclusionLayer {
+ uint32_t light_mask = 1;
+ bool sdf_collision = false;
+ };
+ Vector<OcclusionLayer> occlusion_layers;
+
+ // Physics
+ struct PhysicsLayer {
+ uint32_t collision_layer = 1;
+ uint32_t collision_mask = 1;
+ Ref<PhysicsMaterial> physics_material;
+ };
+ Vector<PhysicsLayer> physics_layers;
+
+ // Terrains
+ struct Terrain {
+ String name;
+ Color color;
+ };
+ struct TerrainSet {
+ TerrainMode mode = TERRAIN_MODE_MATCH_CORNERS_AND_SIDES;
+ Vector<Terrain> terrains;
+ };
+ Vector<TerrainSet> terrain_sets;
+
+ // Navigation
+ struct Navigationlayer {
+ uint32_t layers = 1;
+ };
+ Vector<Navigationlayer> navigation_layers;
+
+ // CustomData
+ struct CustomDataLayer {
+ String name;
+ Variant::Type type = Variant::NIL;
+ };
+ Vector<CustomDataLayer> custom_data_layers;
+ Map<String, int> custom_data_layers_by_name;
+
+ // Per Atlas source data.
+ Map<int, Ref<TileSetSource>> sources;
+ Vector<int> source_ids;
+ int next_source_id = 0;
+ // ---------------------
+
+ // Plugins themselves.
+ Vector<TileSetPlugin *> tile_set_plugins_vector;
+
+ void _compute_next_source_id();
+ void _source_changed();
+
+protected:
static void _bind_methods();
+public:
+ // --- Plugins ---
+ Vector<TileSetPlugin *> get_tile_set_atlas_plugins() const;
+
+ // --- Accessors for TileSet data ---
+
+ // -- Shape and layout --
+ void set_tile_shape(TileShape p_shape);
+ TileShape get_tile_shape() const;
+ void set_tile_layout(TileLayout p_layout);
+ TileLayout get_tile_layout() const;
+ void set_tile_offset_axis(TileOffsetAxis p_alignment);
+ TileOffsetAxis get_tile_offset_axis() const;
+ void set_tile_size(Size2i p_size);
+ Size2i get_tile_size() const;
+ void set_tile_skew(Vector2 p_skew);
+ Vector2 get_tile_skew() const;
+
+ // -- Sources management --
+ int get_next_source_id() const;
+ int get_source_count() const;
+ int get_source_id(int p_index) const;
+ int add_source(Ref<TileSetAtlasSource> p_tile_atlas_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;
+ Ref<TileSetSource> get_source(int p_source_id) const;
+
+ // Rendering
+ void set_y_sorting(bool p_y_sort);
+ bool is_y_sorting() const;
+
+ void set_uv_clipping(bool p_uv_clipping);
+ bool is_uv_clipping() const;
+
+ void set_occlusion_layers_count(int p_occlusion_layers_count);
+ int get_occlusion_layers_count() const;
+ void set_occlusion_layer_light_mask(int p_layer_index, int p_light_mask);
+ int get_occlusion_layer_light_mask(int p_layer_index) const;
+ void set_occlusion_layer_sdf_collision(int p_layer_index, int p_sdf_collision);
+ bool get_occlusion_layer_sdf_collision(int p_layer_index) const;
+
+ // Physics
+ void set_physics_layers_count(int p_physics_layers_count);
+ int get_physics_layers_count() const;
+ void set_physics_layer_collision_layer(int p_layer_index, uint32_t p_layer);
+ uint32_t get_physics_layer_collision_layer(int p_layer_index) const;
+ void set_physics_layer_collision_mask(int p_layer_index, uint32_t p_mask);
+ uint32_t get_physics_layer_collision_mask(int p_layer_index) const;
+ void set_physics_layer_physics_material(int p_layer_index, Ref<PhysicsMaterial> p_physics_material);
+ Ref<PhysicsMaterial> get_physics_layer_physics_material(int p_layer_index) const;
+
+ // Terrains
+ void set_terrain_sets_count(int p_terrains_sets_count);
+ int get_terrain_sets_count() const;
+ void set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode);
+ TerrainMode get_terrain_set_mode(int p_terrain_set) const;
+ void set_terrains_count(int p_terrain_set, int p_terrains_count);
+ int get_terrains_count(int p_terrain_set) const;
+ void set_terrain_name(int p_terrain_set, int p_terrain_index, String p_name);
+ String get_terrain_name(int p_terrain_set, int p_terrain_index) const;
+ void set_terrain_color(int p_terrain_set, int p_terrain_index, Color p_color);
+ Color get_terrain_color(int p_terrain_set, int p_terrain_index) const;
+ bool is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const;
+
+ // Navigation
+ void set_navigation_layers_count(int p_navigation_layers_count);
+ int get_navigation_layers_count() const;
+ void set_navigation_layer_layers(int p_layer_index, uint32_t p_layers);
+ uint32_t get_navigation_layer_layers(int p_layer_index) const;
+
+ // Custom data
+ void set_custom_data_layers_count(int p_custom_data_layers_count);
+ int get_custom_data_layers_count() const;
+ int get_custom_data_layer_by_name(String p_value) const;
+ void set_custom_data_name(int p_layer_id, String p_value);
+ String get_custom_data_name(int p_layer_id) const;
+ void set_custom_data_type(int p_layer_id, Variant::Type p_value);
+ Variant::Type get_custom_data_type(int p_layer_id) const;
+
+ // Helpers
+ void draw_tile_shape(CanvasItem *p_canvas_item, Rect2 p_region, Color p_color, bool p_filled = false, Ref<Texture2D> p_texture = Ref<Texture2D>());
+
virtual void reset_state() override;
+ TileSet();
+ ~TileSet();
+};
+
+class TileSetSource : public Resource {
+ GDCLASS(TileSetSource, Resource);
+
+protected:
+ const TileSet *tile_set = nullptr;
+
public:
- void create_tile(int p_id);
+ // Not exposed.
+ virtual void set_tile_set(const TileSet *p_tile_set);
+ virtual void notify_tile_data_properties_should_change(){};
+ virtual void reset_state() override{};
+
+ // Tiles.
+ virtual int get_tiles_count() const = 0;
+ virtual Vector2i get_tile_id(int tile_index) const = 0;
+ virtual bool has_tile(Vector2i p_atlas_coords) const = 0;
+
+ // Alternative tiles.
+ virtual int get_alternative_tiles_count(const Vector2i p_atlas_coords) const = 0;
+ virtual int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const = 0;
+ virtual bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const = 0;
+};
- void autotile_set_bitmask_mode(int p_id, BitmaskMode p_mode);
- BitmaskMode autotile_get_bitmask_mode(int p_id) const;
+class TileSetAtlasSource : public TileSetSource {
+ GDCLASS(TileSetAtlasSource, TileSetSource);
- void tile_set_name(int p_id, const String &p_name);
- String tile_get_name(int p_id) const;
+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;
+ Map<int, TileData *> alternatives;
+ Vector<int> alternatives_ids;
+ int next_alternative_id = 1;
+ };
- void tile_set_texture(int p_id, const Ref<Texture2D> &p_texture);
- Ref<Texture2D> tile_get_texture(int p_id) const;
+private:
+ Ref<Texture2D> texture;
+ Vector2i margins;
+ Vector2i separation;
+ Size2i texture_region_size = Size2i(16, 16);
- void tile_set_texture_offset(int p_id, const Vector2 &p_offset);
- Vector2 tile_get_texture_offset(int p_id) const;
+ Map<Vector2i, TileAlternativesData> tiles;
+ Vector<Vector2i> tiles_ids;
+ Map<Vector2i, Vector2i> _coords_mapping_cache; // Maps any coordinate to the including tile
- void tile_set_region(int p_id, const Rect2 &p_region);
- Rect2 tile_get_region(int p_id) const;
+ TileData *_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile);
+ const TileData *_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile) const;
- void tile_set_tile_mode(int p_id, TileMode p_tile_mode);
- TileMode tile_get_tile_mode(int p_id) const;
+ void _compute_next_alternative_id(const Vector2i p_atlas_coords);
- void autotile_set_icon_coordinate(int p_id, Vector2 coord);
- Vector2 autotile_get_icon_coordinate(int p_id) const;
+protected:
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
- void autotile_set_spacing(int p_id, int p_spacing);
- int autotile_get_spacing(int p_id) const;
+ static void _bind_methods();
- void autotile_set_size(int p_id, Size2 p_size);
- Size2 autotile_get_size(int p_id) const;
+public:
+ // Not exposed.
+ virtual void set_tile_set(const TileSet *p_tile_set) override;
+ virtual void notify_tile_data_properties_should_change() override;
+ virtual void reset_state() override;
- void autotile_clear_bitmask_map(int p_id);
- void autotile_set_subtile_priority(int p_id, const Vector2 &p_coord, int p_priority);
- int autotile_get_subtile_priority(int p_id, const Vector2 &p_coord);
- const Map<Vector2, int> &autotile_get_priority_map(int p_id) const;
+ // Base properties.
+ void set_texture(Ref<Texture2D> p_texture);
+ Ref<Texture2D> get_texture() const;
+ void set_margins(Vector2i p_margins);
+ Vector2i get_margins() const;
+ void set_separation(Vector2i p_separation);
+ Vector2i get_separation() const;
+ void set_texture_region_size(Vector2i p_tile_size);
+ Vector2i get_texture_region_size() const;
+
+ // Base tiles.
+ void create_tile(const Vector2i p_atlas_coords, const Vector2i p_size = Vector2i(1, 1)); // Create a tile if it does not exists, or add alternative tile if it does.
+ void remove_tile(Vector2i p_atlas_coords); // Remove a tile. If p_tile_key.alternative_tile if different from 0, remove the alternative
+ virtual bool has_tile(Vector2i p_atlas_coords) const override;
+ bool can_move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords = INVALID_ATLAS_COORDS, Vector2i p_new_size = Vector2i(-1, -1)) const;
+ void move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords = INVALID_ATLAS_COORDS, Vector2i p_new_size = Vector2i(-1, -1));
+ Vector2i get_tile_size_in_atlas(Vector2i p_atlas_coords) const;
+
+ virtual int get_tiles_count() const override;
+ virtual Vector2i get_tile_id(int p_index) const override;
+
+ Vector2i get_tile_at_coords(Vector2i p_atlas_coords) const;
+
+ // Alternative tiles.
+ int create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override = -1);
+ void remove_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile);
+ void set_alternative_tile_id(const Vector2i p_atlas_coords, int p_alternative_tile, int p_new_id);
+ virtual bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const override;
+ int get_next_alternative_tile_id(const Vector2i p_atlas_coords) const;
+
+ virtual int get_alternative_tiles_count(const Vector2i p_atlas_coords) const override;
+ virtual int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const override;
+
+ // Get data associated to a tile.
+ Object *get_tile_data(const Vector2i p_atlas_coords, int p_alternative_tile) const;
+
+ // Helpers.
+ Vector2i get_atlas_grid_size() const;
+ bool has_tiles_outside_texture();
+ void clear_tiles_outside_texture();
+ Rect2i get_tile_texture_region(Vector2i p_atlas_coords) const;
+ Vector2i get_tile_effective_texture_offset(Vector2i p_atlas_coords, int p_alternative_tile) const;
+
+ ~TileSetAtlasSource();
+};
- void autotile_set_z_index(int p_id, const Vector2 &p_coord, int p_z_index);
- int autotile_get_z_index(int p_id, const Vector2 &p_coord);
- const Map<Vector2, int> &autotile_get_z_index_map(int p_id) const;
+class TileData : public Object {
+ GDCLASS(TileData, Object);
- void autotile_set_bitmask(int p_id, Vector2 p_coord, uint32_t p_flag);
- uint32_t autotile_get_bitmask(int p_id, Vector2 p_coord);
- const Map<Vector2, uint32_t> &autotile_get_bitmask_map(int p_id);
- Vector2 autotile_get_subtile_for_bitmask(int p_id, uint16_t p_bitmask, const Node *p_tilemap_node = nullptr, const Vector2 &p_tile_location = Vector2());
- Vector2 atlastile_get_subtile_by_priority(int p_id, const Node *p_tilemap_node = nullptr, const Vector2 &p_tile_location = Vector2());
+private:
+ const TileSet *tile_set = nullptr;
+ bool allow_transform = true;
+
+ // Rendering
+ bool flip_h = false;
+ bool flip_v = false;
+ bool transpose = false;
+ Vector2i tex_offset = Vector2i();
+ Ref<ShaderMaterial> material = Ref<ShaderMaterial>();
+ Color modulate = Color(1.0, 1.0, 1.0, 1.0);
+ int z_index = 0;
+ Vector2i y_sort_origin = Vector2i();
+ Vector<Ref<OccluderPolygon2D>> occluders;
+
+ // Physics
+ struct PhysicsLayerTileData {
+ struct ShapeTileData {
+ Ref<Shape2D> shape = Ref<Shape2D>();
+ bool one_way = false;
+ float one_way_margin = 1.0;
+ };
+
+ Vector<ShapeTileData> shapes;
+ };
+ Vector<PhysicsLayerTileData> physics;
+ // TODO add support for areas.
- void tile_set_shape(int p_id, int p_shape_id, const Ref<Shape2D> &p_shape);
- Ref<Shape2D> tile_get_shape(int p_id, int p_shape_id) const;
+ // Terrain
+ int terrain_set = -1;
+ int terrain_peering_bits[16] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
- void tile_set_shape_transform(int p_id, int p_shape_id, const Transform2D &p_offset);
- Transform2D tile_get_shape_transform(int p_id, int p_shape_id) const;
+ // Navigation
+ Vector<Ref<NavigationPolygon>> navigation;
- void tile_set_shape_offset(int p_id, int p_shape_id, const Vector2 &p_offset);
- Vector2 tile_get_shape_offset(int p_id, int p_shape_id) const;
+ // Misc
+ double probability = 1.0;
- void tile_set_shape_one_way(int p_id, int p_shape_id, bool p_one_way);
- bool tile_get_shape_one_way(int p_id, int p_shape_id) const;
+ // Custom data
+ Vector<Variant> custom_data;
- void tile_set_shape_one_way_margin(int p_id, int p_shape_id, float p_margin);
- float tile_get_shape_one_way_margin(int p_id, int p_shape_id) const;
+protected:
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+ static void _bind_methods();
- void tile_clear_shapes(int p_id);
- void tile_add_shape(int p_id, const Ref<Shape2D> &p_shape, const Transform2D &p_transform, bool p_one_way = false, const Vector2 &p_autotile_coord = Vector2());
- int tile_get_shape_count(int p_id) const;
+public:
+ // Not exposed.
+ void set_tile_set(const TileSet *p_tile_set);
+ void notify_tile_data_properties_should_change();
+ void reset_state();
+ void set_allow_transform(bool p_allow_transform);
+ bool is_allowing_transform() const;
+
+ // Rendering
+ void set_flip_h(bool p_flip_h);
+ bool get_flip_h() const;
+ void set_flip_v(bool p_flip_v);
+ bool get_flip_v() const;
+ void set_transpose(bool p_transpose);
+ bool get_transpose() const;
+
+ void set_texture_offset(Vector2i p_texture_offset);
+ Vector2i get_texture_offset() const;
+ void tile_set_material(Ref<ShaderMaterial> p_material);
+ Ref<ShaderMaterial> tile_get_material() const;
+ void set_modulate(Color p_modulate);
+ Color get_modulate() const;
+ void set_z_index(int p_z_index);
+ int get_z_index() const;
+ void set_y_sort_origin(Vector2i p_y_sort_origin);
+ Vector2i get_y_sort_origin() const;
+
+ void set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon);
+ Ref<OccluderPolygon2D> get_occluder(int p_layer_id) const;
+
+ // Physics
+ int get_collision_shapes_count(int p_layer_id) const;
+ void set_collision_shapes_count(int p_layer_id, int p_shapes_count);
+ void add_collision_shape(int p_layer_id);
+ void remove_collision_shape(int p_layer_id, int p_shape_index);
+ void set_collision_shape_shape(int p_layer_id, int p_shape_index, Ref<Shape2D> p_shape);
+ Ref<Shape2D> get_collision_shape_shape(int p_layer_id, int p_shape_index) const;
+ void set_collision_shape_one_way(int p_layer_id, int p_shape_index, bool p_one_way);
+ bool is_collision_shape_one_way(int p_layer_id, int p_shape_index) const;
+ void set_collision_shape_one_way_margin(int p_layer_id, int p_shape_index, float p_one_way_margin);
+ float get_collision_shape_one_way_margin(int p_layer_id, int p_shape_index) const;
+
+ // Terrain
+ void set_terrain_set(int p_terrain_id);
+ int get_terrain_set() const;
+ void set_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit, int p_terrain_id);
+ int get_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const;
+ bool is_valid_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const;
+
+ // Navigation
+ void set_navigation_polygon(int p_layer_id, Ref<NavigationPolygon> p_navigation_polygon);
+ Ref<NavigationPolygon> get_navigation_polygon(int p_layer_id) const;
+
+ // Misc
+ void set_probability(float p_probability);
+ float get_probability() const;
+
+ // Custom data.
+ void set_custom_data(String p_layer_name, Variant p_value);
+ Variant get_custom_data(String p_layer_name) const;
+ void set_custom_data_by_layer_id(int p_layer_id, Variant p_value);
+ Variant get_custom_data_by_layer_id(int p_layer_id) const;
+};
- void tile_set_shapes(int p_id, const Vector<ShapeData> &p_shapes);
- Vector<ShapeData> tile_get_shapes(int p_id) const;
+#include "scene/2d/tile_map.h"
- void tile_set_material(int p_id, const Ref<ShaderMaterial> &p_material);
- Ref<ShaderMaterial> tile_get_material(int p_id) const;
+class TileSetPlugin : public Object {
+ GDCLASS(TileSetPlugin, Object);
- void tile_set_modulate(int p_id, const Color &p_modulate);
- Color tile_get_modulate(int p_id) const;
+public:
+ // Tilemap updates.
+ virtual void tilemap_notification(TileMap *p_tile_map, int p_what){};
+ virtual void update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list){};
+ virtual void create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant){};
+ virtual void cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant){};
- void tile_set_occluder_offset(int p_id, const Vector2 &p_offset);
- Vector2 tile_get_occluder_offset(int p_id) const;
+ virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant){};
+};
- void tile_set_light_occluder(int p_id, const Ref<OccluderPolygon2D> &p_light_occluder);
- Ref<OccluderPolygon2D> tile_get_light_occluder(int p_id) const;
+class TileSetAtlasPluginRendering : public TileSetPlugin {
+ GDCLASS(TileSetAtlasPluginRendering, TileSetPlugin);
- void autotile_set_light_occluder(int p_id, const Ref<OccluderPolygon2D> &p_light_occluder, const Vector2 &p_coord);
- Ref<OccluderPolygon2D> autotile_get_light_occluder(int p_id, const Vector2 &p_coord) const;
- const Map<Vector2, Ref<OccluderPolygon2D>> &autotile_get_light_oclusion_map(int p_id) const;
+private:
+ static constexpr float fp_adjust = 0.00001;
+ bool quadrant_order_dirty = false;
- void tile_set_navigation_polygon_offset(int p_id, const Vector2 &p_offset);
- Vector2 tile_get_navigation_polygon_offset(int p_id) const;
+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;
+
+ // 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));
+};
- void tile_set_navigation_polygon(int p_id, const Ref<NavigationPolygon> &p_navigation_polygon);
- Ref<NavigationPolygon> tile_get_navigation_polygon(int p_id) const;
+class TileSetAtlasPluginTerrain : public TileSetPlugin {
+ GDCLASS(TileSetAtlasPluginTerrain, TileSetPlugin);
- void autotile_set_navigation_polygon(int p_id, const Ref<NavigationPolygon> &p_navigation_polygon, const Vector2 &p_coord);
- Ref<NavigationPolygon> autotile_get_navigation_polygon(int p_id, const Vector2 &p_coord) const;
- const Map<Vector2, Ref<NavigationPolygon>> &autotile_get_navigation_map(int p_id) const;
+private:
+ static void _draw_square_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit);
+ static void _draw_square_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit);
+ static void _draw_square_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit);
- void tile_set_z_index(int p_id, int p_z_index);
- int tile_get_z_index(int p_id) const;
+ static void _draw_isometric_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit);
+ static void _draw_isometric_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit);
+ static void _draw_isometric_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit);
- void remove_tile(int p_id);
+ static void _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);
+ static void _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);
+ 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);
- bool has_tile(int p_id) const;
+public:
+ //virtual void tilemap_notification(const TileMap * p_tile_map, int p_what);
- bool is_tile_bound(int p_drawn_id, int p_neighbor_id);
+ static void draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, const TileData *p_tile_data);
+};
- int find_tile_by_name(const String &p_name) const;
- void get_tile_list(List<int> *p_tiles) const;
+class TileSetAtlasPluginPhysics : public TileSetPlugin {
+ GDCLASS(TileSetAtlasPluginPhysics, TileSetPlugin);
- void clear();
+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;
+};
- int get_last_unused_tile_id() const;
+class TileSetAtlasPluginNavigation : public TileSetPlugin {
+ GDCLASS(TileSetAtlasPluginNavigation, TileSetPlugin);
- TileSet();
+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;
};
-VARIANT_ENUM_CAST(TileSet::AutotileBindings);
-VARIANT_ENUM_CAST(TileSet::BitmaskMode);
-VARIANT_ENUM_CAST(TileSet::TileMode);
+VARIANT_ENUM_CAST(TileSet::CellNeighbor);
+VARIANT_ENUM_CAST(TileSet::TerrainMode);
+VARIANT_ENUM_CAST(TileSet::TileShape);
+VARIANT_ENUM_CAST(TileSet::TileLayout);
+VARIANT_ENUM_CAST(TileSet::TileOffsetAxis);
#endif // TILE_SET_H
diff --git a/servers/audio/effects/audio_effect_pitch_shift.cpp b/servers/audio/effects/audio_effect_pitch_shift.cpp
index 2123fe8548..7b0151b9c1 100644
--- a/servers/audio/effects/audio_effect_pitch_shift.cpp
+++ b/servers/audio/effects/audio_effect_pitch_shift.cpp
@@ -326,12 +326,12 @@ int AudioEffectPitchShift::get_oversampling() const {
return oversampling;
}
-void AudioEffectPitchShift::set_fft_size(FFT_Size p_fft_size) {
+void AudioEffectPitchShift::set_fft_size(FFTSize p_fft_size) {
ERR_FAIL_INDEX(p_fft_size, FFT_SIZE_MAX);
fft_size = p_fft_size;
}
-AudioEffectPitchShift::FFT_Size AudioEffectPitchShift::get_fft_size() const {
+AudioEffectPitchShift::FFTSize AudioEffectPitchShift::get_fft_size() const {
return fft_size;
}
diff --git a/servers/audio/effects/audio_effect_pitch_shift.h b/servers/audio/effects/audio_effect_pitch_shift.h
index 18a9c33968..669943fa43 100644
--- a/servers/audio/effects/audio_effect_pitch_shift.h
+++ b/servers/audio/effects/audio_effect_pitch_shift.h
@@ -89,7 +89,7 @@ class AudioEffectPitchShift : public AudioEffect {
public:
friend class AudioEffectPitchShiftInstance;
- enum FFT_Size {
+ enum FFTSize {
FFT_SIZE_256,
FFT_SIZE_512,
FFT_SIZE_1024,
@@ -100,7 +100,7 @@ public:
float pitch_scale;
int oversampling;
- FFT_Size fft_size;
+ FFTSize fft_size;
float wet;
float dry;
bool filter;
@@ -117,12 +117,12 @@ public:
void set_oversampling(int p_oversampling);
int get_oversampling() const;
- void set_fft_size(FFT_Size);
- FFT_Size get_fft_size() const;
+ void set_fft_size(FFTSize);
+ FFTSize get_fft_size() const;
AudioEffectPitchShift();
};
-VARIANT_ENUM_CAST(AudioEffectPitchShift::FFT_Size);
+VARIANT_ENUM_CAST(AudioEffectPitchShift::FFTSize);
#endif // AUDIO_EFFECT_PITCH_SHIFT_H
diff --git a/servers/audio/effects/audio_effect_spectrum_analyzer.cpp b/servers/audio/effects/audio_effect_spectrum_analyzer.cpp
index 3f7ab74a74..44b7f64d52 100644
--- a/servers/audio/effects/audio_effect_spectrum_analyzer.cpp
+++ b/servers/audio/effects/audio_effect_spectrum_analyzer.cpp
@@ -245,12 +245,12 @@ float AudioEffectSpectrumAnalyzer::get_tap_back_pos() const {
return tapback_pos;
}
-void AudioEffectSpectrumAnalyzer::set_fft_size(FFT_Size p_fft_size) {
+void AudioEffectSpectrumAnalyzer::set_fft_size(FFTSize p_fft_size) {
ERR_FAIL_INDEX(p_fft_size, FFT_SIZE_MAX);
fft_size = p_fft_size;
}
-AudioEffectSpectrumAnalyzer::FFT_Size AudioEffectSpectrumAnalyzer::get_fft_size() const {
+AudioEffectSpectrumAnalyzer::FFTSize AudioEffectSpectrumAnalyzer::get_fft_size() const {
return fft_size;
}
diff --git a/servers/audio/effects/audio_effect_spectrum_analyzer.h b/servers/audio/effects/audio_effect_spectrum_analyzer.h
index fba276e2bb..fc275446f0 100644
--- a/servers/audio/effects/audio_effect_spectrum_analyzer.h
+++ b/servers/audio/effects/audio_effect_spectrum_analyzer.h
@@ -71,7 +71,7 @@ class AudioEffectSpectrumAnalyzer : public AudioEffect {
GDCLASS(AudioEffectSpectrumAnalyzer, AudioEffect);
public:
- enum FFT_Size {
+ enum FFTSize {
FFT_SIZE_256,
FFT_SIZE_512,
FFT_SIZE_1024,
@@ -84,7 +84,7 @@ public:
friend class AudioEffectSpectrumAnalyzerInstance;
float buffer_length;
float tapback_pos;
- FFT_Size fft_size;
+ FFTSize fft_size;
protected:
static void _bind_methods();
@@ -96,12 +96,12 @@ public:
void set_tap_back_pos(float p_seconds);
float get_tap_back_pos() const;
- void set_fft_size(FFT_Size);
- FFT_Size get_fft_size() const;
+ void set_fft_size(FFTSize);
+ FFTSize get_fft_size() const;
AudioEffectSpectrumAnalyzer();
};
-VARIANT_ENUM_CAST(AudioEffectSpectrumAnalyzer::FFT_Size);
+VARIANT_ENUM_CAST(AudioEffectSpectrumAnalyzer::FFTSize);
#endif // AUDIO_EFFECT_SPECTRUM_ANALYZER_H
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index 2fa333cc05..7bd1075006 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -253,27 +253,6 @@ bool DisplayServer::get_swap_cancel_ok() {
void DisplayServer::enable_for_stealing_focus(OS::ProcessID pid) {
}
-//plays video natively, in fullscreen, only implemented in mobile for now, likely not possible to implement on linux also.
-Error DisplayServer::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen) {
- ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "Native video not supported by this display server.");
-}
-
-bool DisplayServer::native_video_is_playing() const {
- return false;
-}
-
-void DisplayServer::native_video_pause() {
- WARN_PRINT("Native video not supported by this display server.");
-}
-
-void DisplayServer::native_video_unpause() {
- WARN_PRINT("Native video not supported by this display server.");
-}
-
-void DisplayServer::native_video_stop() {
- WARN_PRINT("Native video not supported by this display server.");
-}
-
Error DisplayServer::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) {
WARN_PRINT("Native dialogs not supported by this display server.");
return OK;
@@ -477,12 +456,6 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("enable_for_stealing_focus", "process_id"), &DisplayServer::enable_for_stealing_focus);
- ClassDB::bind_method(D_METHOD("native_video_play", "path", "volume", "audio_track", "subtitle_track", "screen"), &DisplayServer::native_video_play);
- ClassDB::bind_method(D_METHOD("native_video_is_playing"), &DisplayServer::native_video_is_playing);
- ClassDB::bind_method(D_METHOD("native_video_stop"), &DisplayServer::native_video_stop);
- ClassDB::bind_method(D_METHOD("native_video_pause"), &DisplayServer::native_video_pause);
- ClassDB::bind_method(D_METHOD("native_video_unpause"), &DisplayServer::native_video_unpause);
-
ClassDB::bind_method(D_METHOD("dialog_show", "title", "description", "buttons", "callback"), &DisplayServer::dialog_show);
ClassDB::bind_method(D_METHOD("dialog_input_text", "title", "description", "existing_text", "callback"), &DisplayServer::dialog_input_text);
@@ -518,7 +491,6 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(FEATURE_VIRTUAL_KEYBOARD);
BIND_ENUM_CONSTANT(FEATURE_CURSOR_SHAPE);
BIND_ENUM_CONSTANT(FEATURE_CUSTOM_CURSOR_SHAPE);
- BIND_ENUM_CONSTANT(FEATURE_NATIVE_VIDEO);
BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG);
BIND_ENUM_CONSTANT(FEATURE_CONSOLE_WINDOW);
BIND_ENUM_CONSTANT(FEATURE_IME);
diff --git a/servers/display_server.h b/servers/display_server.h
index 3aab572120..f05aa1f59a 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -97,7 +97,6 @@ public:
FEATURE_VIRTUAL_KEYBOARD,
FEATURE_CURSOR_SHAPE,
FEATURE_CUSTOM_CURSOR_SHAPE,
- FEATURE_NATIVE_VIDEO,
FEATURE_NATIVE_DIALOG,
FEATURE_CONSOLE_WINDOW,
FEATURE_IME,
@@ -324,13 +323,6 @@ public:
virtual void enable_for_stealing_focus(OS::ProcessID pid);
- //plays video natively, in fullscreen, only implemented in mobile for now, likely not possible to implement on linux also.
- virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen = SCREEN_OF_MAIN_WINDOW);
- virtual bool native_video_is_playing() const;
- virtual void native_video_pause();
- virtual void native_video_unpause();
- virtual void native_video_stop();
-
virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback);
virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback);
diff --git a/servers/physics_2d/broad_phase_2d_basic.cpp b/servers/physics_2d/broad_phase_2d_basic.cpp
deleted file mode 100644
index 17424629a9..0000000000
--- a/servers/physics_2d/broad_phase_2d_basic.cpp
+++ /dev/null
@@ -1,174 +0,0 @@
-/*************************************************************************/
-/* broad_phase_2d_basic.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 "broad_phase_2d_basic.h"
-
-BroadPhase2DBasic::ID BroadPhase2DBasic::create(CollisionObject2DSW *p_object_, int p_subindex) {
- current++;
-
- Element e;
- e.owner = p_object_;
- e._static = false;
- e.subindex = p_subindex;
-
- element_map[current] = e;
- return current;
-}
-
-void BroadPhase2DBasic::move(ID p_id, const Rect2 &p_aabb) {
- Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND(!E);
- E->get().aabb = p_aabb;
-}
-
-void BroadPhase2DBasic::set_static(ID p_id, bool p_static) {
- Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND(!E);
- E->get()._static = p_static;
-}
-
-void BroadPhase2DBasic::remove(ID p_id) {
- Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND(!E);
- element_map.erase(E);
-}
-
-CollisionObject2DSW *BroadPhase2DBasic::get_object(ID p_id) const {
- const Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND_V(!E, nullptr);
- return E->get().owner;
-}
-
-bool BroadPhase2DBasic::is_static(ID p_id) const {
- const Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND_V(!E, false);
- return E->get()._static;
-}
-
-int BroadPhase2DBasic::get_subindex(ID p_id) const {
- const Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND_V(!E, -1);
- return E->get().subindex;
-}
-
-int BroadPhase2DBasic::cull_segment(const Vector2 &p_from, const Vector2 &p_to, CollisionObject2DSW **p_results, int p_max_results, int *p_result_indices) {
- int rc = 0;
-
- for (Map<ID, Element>::Element *E = element_map.front(); E; E = E->next()) {
- const Rect2 aabb = E->get().aabb;
- if (aabb.intersects_segment(p_from, p_to)) {
- p_results[rc] = E->get().owner;
- p_result_indices[rc] = E->get().subindex;
- rc++;
- if (rc >= p_max_results) {
- break;
- }
- }
- }
-
- return rc;
-}
-
-int BroadPhase2DBasic::cull_aabb(const Rect2 &p_aabb, CollisionObject2DSW **p_results, int p_max_results, int *p_result_indices) {
- int rc = 0;
-
- for (Map<ID, Element>::Element *E = element_map.front(); E; E = E->next()) {
- const Rect2 aabb = E->get().aabb;
- if (aabb.intersects(p_aabb)) {
- p_results[rc] = E->get().owner;
- p_result_indices[rc] = E->get().subindex;
- rc++;
- if (rc >= p_max_results) {
- break;
- }
- }
- }
-
- return rc;
-}
-
-void BroadPhase2DBasic::set_pair_callback(PairCallback p_pair_callback, void *p_userdata) {
- pair_userdata = p_userdata;
- pair_callback = p_pair_callback;
-}
-
-void BroadPhase2DBasic::set_unpair_callback(UnpairCallback p_unpair_callback, void *p_userdata) {
- unpair_userdata = p_userdata;
- unpair_callback = p_unpair_callback;
-}
-
-void BroadPhase2DBasic::update() {
- // recompute pairs
- for (Map<ID, Element>::Element *I = element_map.front(); I; I = I->next()) {
- for (Map<ID, Element>::Element *J = I->next(); J; J = J->next()) {
- Element *elem_A = &I->get();
- Element *elem_B = &J->get();
-
- if (elem_A->owner == elem_B->owner) {
- continue;
- }
-
- bool pair_ok = elem_A->aabb.intersects(elem_B->aabb) && (!elem_A->_static || !elem_B->_static);
-
- PairKey key(I->key(), J->key());
-
- Map<PairKey, void *>::Element *E = pair_map.find(key);
-
- if (!pair_ok && E) {
- if (unpair_callback) {
- unpair_callback(elem_A->owner, elem_A->subindex, elem_B->owner, elem_B->subindex, E->get(), unpair_userdata);
- }
- pair_map.erase(key);
- }
-
- if (pair_ok && !E) {
- void *data = nullptr;
- if (pair_callback) {
- data = pair_callback(elem_A->owner, elem_A->subindex, elem_B->owner, elem_B->subindex, unpair_userdata);
- if (data) {
- pair_map.insert(key, data);
- }
- }
- }
- }
- }
-}
-
-BroadPhase2DSW *BroadPhase2DBasic::_create() {
- return memnew(BroadPhase2DBasic);
-}
-
-BroadPhase2DBasic::BroadPhase2DBasic() {
- current = 1;
- unpair_callback = nullptr;
- unpair_userdata = nullptr;
- pair_callback = nullptr;
- pair_userdata = nullptr;
-}
diff --git a/servers/physics_2d/broad_phase_2d_bvh.cpp b/servers/physics_2d/broad_phase_2d_bvh.cpp
new file mode 100644
index 0000000000..5f53f4a012
--- /dev/null
+++ b/servers/physics_2d/broad_phase_2d_bvh.cpp
@@ -0,0 +1,116 @@
+/*************************************************************************/
+/* broad_phase_2d_bvh.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 "broad_phase_2d_bvh.h"
+#include "collision_object_2d_sw.h"
+
+BroadPhase2DSW::ID BroadPhase2DBVH::create(CollisionObject2DSW *p_object, int p_subindex, const Rect2 &p_aabb, bool p_static) {
+ ID oid = bvh.create(p_object, true, p_aabb, p_subindex, !p_static, 1 << p_object->get_type(), p_static ? 0 : 0xFFFFF); // Pair everything, don't care?
+ return oid + 1;
+}
+
+void BroadPhase2DBVH::move(ID p_id, const Rect2 &p_aabb) {
+ bvh.move(p_id - 1, p_aabb);
+}
+
+void BroadPhase2DBVH::set_static(ID p_id, bool p_static) {
+ CollisionObject2DSW *it = bvh.get(p_id - 1);
+ bvh.set_pairable(p_id - 1, !p_static, 1 << it->get_type(), p_static ? 0 : 0xFFFFF, false); // Pair everything, don't care?
+}
+
+void BroadPhase2DBVH::remove(ID p_id) {
+ bvh.erase(p_id - 1);
+}
+
+CollisionObject2DSW *BroadPhase2DBVH::get_object(ID p_id) const {
+ CollisionObject2DSW *it = bvh.get(p_id - 1);
+ ERR_FAIL_COND_V(!it, nullptr);
+ return it;
+}
+
+bool BroadPhase2DBVH::is_static(ID p_id) const {
+ return !bvh.is_pairable(p_id - 1);
+}
+
+int BroadPhase2DBVH::get_subindex(ID p_id) const {
+ return bvh.get_subindex(p_id - 1);
+}
+
+int BroadPhase2DBVH::cull_segment(const Vector2 &p_from, const Vector2 &p_to, CollisionObject2DSW **p_results, int p_max_results, int *p_result_indices) {
+ return bvh.cull_segment(p_from, p_to, p_results, p_max_results, p_result_indices);
+}
+
+int BroadPhase2DBVH::cull_aabb(const Rect2 &p_aabb, CollisionObject2DSW **p_results, int p_max_results, int *p_result_indices) {
+ return bvh.cull_aabb(p_aabb, p_results, p_max_results, p_result_indices);
+}
+
+void *BroadPhase2DBVH::_pair_callback(void *self, uint32_t p_A, CollisionObject2DSW *p_object_A, int subindex_A, uint32_t p_B, CollisionObject2DSW *p_object_B, int subindex_B) {
+ BroadPhase2DBVH *bpo = (BroadPhase2DBVH *)(self);
+ if (!bpo->pair_callback) {
+ return nullptr;
+ }
+
+ return bpo->pair_callback(p_object_A, subindex_A, p_object_B, subindex_B, bpo->pair_userdata);
+}
+
+void BroadPhase2DBVH::_unpair_callback(void *self, uint32_t p_A, CollisionObject2DSW *p_object_A, int subindex_A, uint32_t p_B, CollisionObject2DSW *p_object_B, int subindex_B, void *pairdata) {
+ BroadPhase2DBVH *bpo = (BroadPhase2DBVH *)(self);
+ if (!bpo->unpair_callback) {
+ return;
+ }
+
+ bpo->unpair_callback(p_object_A, subindex_A, p_object_B, subindex_B, pairdata, bpo->unpair_userdata);
+}
+
+void BroadPhase2DBVH::set_pair_callback(PairCallback p_pair_callback, void *p_userdata) {
+ pair_callback = p_pair_callback;
+ pair_userdata = p_userdata;
+}
+
+void BroadPhase2DBVH::set_unpair_callback(UnpairCallback p_unpair_callback, void *p_userdata) {
+ unpair_callback = p_unpair_callback;
+ unpair_userdata = p_userdata;
+}
+
+void BroadPhase2DBVH::update() {
+ bvh.update();
+}
+
+BroadPhase2DSW *BroadPhase2DBVH::_create() {
+ return memnew(BroadPhase2DBVH);
+}
+
+BroadPhase2DBVH::BroadPhase2DBVH() {
+ bvh.set_pair_callback(_pair_callback, this);
+ bvh.set_unpair_callback(_unpair_callback, this);
+ pair_callback = nullptr;
+ pair_userdata = nullptr;
+ unpair_userdata = nullptr;
+}
diff --git a/servers/physics_2d/broad_phase_2d_basic.h b/servers/physics_2d/broad_phase_2d_bvh.h
index ca1db360fb..6c11d2561b 100644
--- a/servers/physics_2d/broad_phase_2d_basic.h
+++ b/servers/physics_2d/broad_phase_2d_bvh.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* broad_phase_2d_basic.h */
+/* broad_phase_2d_bvh.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,49 +28,19 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef BROAD_PHASE_2D_BASIC_H
-#define BROAD_PHASE_2D_BASIC_H
+#ifndef BROAD_PHASE_2D_BVH_H
+#define BROAD_PHASE_2D_BVH_H
-#include "core/templates/map.h"
-#include "space_2d_sw.h"
-class BroadPhase2DBasic : public BroadPhase2DSW {
- struct Element {
- CollisionObject2DSW *owner;
- bool _static;
- Rect2 aabb;
- int subindex;
- };
+#include "broad_phase_2d_sw.h"
+#include "core/math/bvh.h"
+#include "core/math/rect2.h"
+#include "core/math/vector2.h"
- Map<ID, Element> element_map;
+class BroadPhase2DBVH : public BroadPhase2DSW {
+ BVH_Manager<CollisionObject2DSW, true, 128, Rect2, Vector2> bvh;
- ID current;
-
- struct PairKey {
- union {
- struct {
- ID a;
- ID b;
- };
- uint64_t key;
- };
-
- _FORCE_INLINE_ bool operator<(const PairKey &p_key) const {
- return key < p_key.key;
- }
-
- PairKey() { key = 0; }
- PairKey(ID p_a, ID p_b) {
- if (p_a > p_b) {
- a = p_b;
- b = p_a;
- } else {
- a = p_a;
- b = p_b;
- }
- }
- };
-
- Map<PairKey, void *> pair_map;
+ static void *_pair_callback(void *, uint32_t, CollisionObject2DSW *, int, uint32_t, CollisionObject2DSW *, int);
+ static void _unpair_callback(void *, uint32_t, CollisionObject2DSW *, int, uint32_t, CollisionObject2DSW *, int, void *);
PairCallback pair_callback;
void *pair_userdata;
@@ -79,7 +49,7 @@ class BroadPhase2DBasic : public BroadPhase2DSW {
public:
// 0 is an invalid ID
- virtual ID create(CollisionObject2DSW *p_object_, int p_subindex = 0);
+ virtual ID create(CollisionObject2DSW *p_object, int p_subindex = 0, const Rect2 &p_aabb = Rect2(), bool p_static = false);
virtual void move(ID p_id, const Rect2 &p_aabb);
virtual void set_static(ID p_id, bool p_static);
virtual void remove(ID p_id);
@@ -97,7 +67,7 @@ public:
virtual void update();
static BroadPhase2DSW *_create();
- BroadPhase2DBasic();
+ BroadPhase2DBVH();
};
-#endif // BROAD_PHASE_2D_BASIC_H
+#endif // BROAD_PHASE_2D_BVH_H
diff --git a/servers/physics_2d/broad_phase_2d_hash_grid.cpp b/servers/physics_2d/broad_phase_2d_hash_grid.cpp
deleted file mode 100644
index 35447c5389..0000000000
--- a/servers/physics_2d/broad_phase_2d_hash_grid.cpp
+++ /dev/null
@@ -1,738 +0,0 @@
-/*************************************************************************/
-/* broad_phase_2d_hash_grid.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 "broad_phase_2d_hash_grid.h"
-#include "collision_object_2d_sw.h"
-#include "core/config/project_settings.h"
-
-#define LARGE_ELEMENT_FI 1.01239812
-
-void BroadPhase2DHashGrid::_pair_attempt(Element *p_elem, Element *p_with) {
- if (p_elem->owner == p_with->owner) {
- return;
- }
- if (!_test_collision_mask(p_elem->collision_mask, p_elem->collision_layer, p_with->collision_mask, p_with->collision_layer)) {
- return;
- }
- Map<Element *, PairData *>::Element *E = p_elem->paired.find(p_with);
-
- ERR_FAIL_COND(p_elem->_static && p_with->_static);
-
- if (!E) {
- PairData *pd = memnew(PairData);
- p_elem->paired[p_with] = pd;
- p_with->paired[p_elem] = pd;
- } else {
- E->get()->rc++;
- }
-}
-
-void BroadPhase2DHashGrid::_unpair_attempt(Element *p_elem, Element *p_with) {
- if (p_elem->owner == p_with->owner) {
- return;
- }
- if (!_test_collision_mask(p_elem->collision_mask, p_elem->collision_layer, p_with->collision_mask, p_with->collision_layer)) {
- return;
- }
- Map<Element *, PairData *>::Element *E = p_elem->paired.find(p_with);
-
- ERR_FAIL_COND(!E); //this should really be paired..
-
- E->get()->rc--;
-
- if (E->get()->rc == 0) {
- if (E->get()->colliding) {
- //uncollide
- if (unpair_callback) {
- unpair_callback(p_elem->owner, p_elem->subindex, p_with->owner, p_with->subindex, E->get()->ud, unpair_userdata);
- }
- }
-
- memdelete(E->get());
- p_elem->paired.erase(E);
- p_with->paired.erase(p_elem);
- }
-}
-
-void BroadPhase2DHashGrid::_check_motion(Element *p_elem) {
- for (Map<Element *, PairData *>::Element *E = p_elem->paired.front(); E; E = E->next()) {
- bool physical_collision = p_elem->aabb.intersects(E->key()->aabb);
- bool logical_collision = p_elem->owner->test_collision_mask(E->key()->owner);
-
- if (physical_collision && logical_collision) {
- if (!E->get()->colliding && pair_callback) {
- E->get()->ud = pair_callback(p_elem->owner, p_elem->subindex, E->key()->owner, E->key()->subindex, pair_userdata);
- }
- E->get()->colliding = true;
- } else { // No collision
- if (E->get()->colliding && unpair_callback) {
- unpair_callback(p_elem->owner, p_elem->subindex, E->key()->owner, E->key()->subindex, E->get()->ud, unpair_userdata);
- E->get()->ud = nullptr;
- }
- E->get()->colliding = false;
- }
- }
-}
-
-void BroadPhase2DHashGrid::_enter_grid(Element *p_elem, const Rect2 &p_rect, bool p_static, bool p_force_enter) {
- Vector2 sz = (p_rect.size / cell_size * LARGE_ELEMENT_FI); //use magic number to avoid floating point issues
- if (sz.width * sz.height > large_object_min_surface) {
- //large object, do not use grid, must check against all elements
- for (Map<ID, Element>::Element *E = element_map.front(); E; E = E->next()) {
- if (E->key() == p_elem->self) {
- continue; // do not pair against itself
- }
- if (E->get()._static && p_static) {
- continue;
- }
-
- _pair_attempt(p_elem, &E->get());
- }
-
- large_elements[p_elem].inc();
- return;
- }
-
- Point2i from = (p_rect.position / cell_size).floor();
- Point2i to = ((p_rect.position + p_rect.size) / cell_size).floor();
-
- for (int i = from.x; i <= to.x; i++) {
- for (int j = from.y; j <= to.y; j++) {
- PosKey pk;
- pk.x = i;
- pk.y = j;
-
- uint32_t idx = pk.hash() % hash_table_size;
- PosBin *pb = hash_table[idx];
-
- while (pb) {
- if (pb->key == pk) {
- break;
- }
-
- pb = pb->next;
- }
-
- bool entered = p_force_enter;
-
- if (!pb) {
- //does not exist, create!
- pb = memnew(PosBin);
- pb->key = pk;
- pb->next = hash_table[idx];
- hash_table[idx] = pb;
- }
-
- if (p_static) {
- if (pb->static_object_set[p_elem].inc() == 1) {
- entered = true;
- }
- } else {
- if (pb->object_set[p_elem].inc() == 1) {
- entered = true;
- }
- }
-
- if (entered) {
- for (Map<Element *, RC>::Element *E = pb->object_set.front(); E; E = E->next()) {
- _pair_attempt(p_elem, E->key());
- }
-
- if (!p_static) {
- for (Map<Element *, RC>::Element *E = pb->static_object_set.front(); E; E = E->next()) {
- _pair_attempt(p_elem, E->key());
- }
- }
- }
- }
- }
-
- //pair separatedly with large elements
-
- for (Map<Element *, RC>::Element *E = large_elements.front(); E; E = E->next()) {
- if (E->key() == p_elem) {
- continue; // do not pair against itself
- }
- if (E->key()->_static && p_static) {
- continue;
- }
- _pair_attempt(E->key(), p_elem);
- }
-}
-
-void BroadPhase2DHashGrid::_exit_grid(Element *p_elem, const Rect2 &p_rect, bool p_static, bool p_force_exit) {
- Vector2 sz = (p_rect.size / cell_size * LARGE_ELEMENT_FI);
- if (sz.width * sz.height > large_object_min_surface) {
- //unpair all elements, instead of checking all, just check what is already paired, so we at least save from checking static vs static
- Map<Element *, PairData *>::Element *E = p_elem->paired.front();
- while (E) {
- Map<Element *, PairData *>::Element *next = E->next();
- _unpair_attempt(p_elem, E->key());
- E = next;
- }
-
- if (large_elements[p_elem].dec() == 0) {
- large_elements.erase(p_elem);
- }
- return;
- }
-
- Point2i from = (p_rect.position / cell_size).floor();
- Point2i to = ((p_rect.position + p_rect.size) / cell_size).floor();
-
- for (int i = from.x; i <= to.x; i++) {
- for (int j = from.y; j <= to.y; j++) {
- PosKey pk;
- pk.x = i;
- pk.y = j;
-
- uint32_t idx = pk.hash() % hash_table_size;
- PosBin *pb = hash_table[idx];
-
- while (pb) {
- if (pb->key == pk) {
- break;
- }
-
- pb = pb->next;
- }
-
- ERR_CONTINUE(!pb); //should exist!!
-
- bool exited = p_force_exit;
-
- if (p_static) {
- if (pb->static_object_set[p_elem].dec() == 0) {
- pb->static_object_set.erase(p_elem);
- exited = true;
- }
- } else {
- if (pb->object_set[p_elem].dec() == 0) {
- pb->object_set.erase(p_elem);
- exited = true;
- }
- }
-
- if (exited) {
- for (Map<Element *, RC>::Element *E = pb->object_set.front(); E; E = E->next()) {
- _unpair_attempt(p_elem, E->key());
- }
-
- if (!p_static) {
- for (Map<Element *, RC>::Element *E = pb->static_object_set.front(); E; E = E->next()) {
- _unpair_attempt(p_elem, E->key());
- }
- }
- }
-
- if (pb->object_set.is_empty() && pb->static_object_set.is_empty()) {
- if (hash_table[idx] == pb) {
- hash_table[idx] = pb->next;
- } else {
- PosBin *px = hash_table[idx];
-
- while (px) {
- if (px->next == pb) {
- px->next = pb->next;
- break;
- }
-
- px = px->next;
- }
-
- ERR_CONTINUE(!px);
- }
-
- memdelete(pb);
- }
- }
- }
-
- for (Map<Element *, RC>::Element *E = large_elements.front(); E; E = E->next()) {
- if (E->key() == p_elem) {
- continue; // do not pair against itself
- }
- if (E->key()->_static && p_static) {
- continue;
- }
-
- //unpair from large elements
- _unpair_attempt(p_elem, E->key());
- }
-}
-
-BroadPhase2DHashGrid::ID BroadPhase2DHashGrid::create(CollisionObject2DSW *p_object, int p_subindex) {
- current++;
-
- Element e;
- e.owner = p_object;
- e._static = false;
- e.collision_mask = p_object->get_collision_mask();
- e.collision_layer = p_object->get_collision_layer();
- e.subindex = p_subindex;
- e.self = current;
- e.pass = 0;
-
- element_map[current] = e;
- return current;
-}
-
-void BroadPhase2DHashGrid::move(ID p_id, const Rect2 &p_aabb) {
- Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND(!E);
-
- Element &e = E->get();
- bool layer_changed = e.collision_mask != e.owner->get_collision_mask() || e.collision_layer != e.owner->get_collision_layer();
-
- if (p_aabb != e.aabb || layer_changed) {
- uint32_t old_mask = e.collision_mask;
- uint32_t old_layer = e.collision_layer;
- if (p_aabb != Rect2()) {
- e.collision_mask = e.owner->get_collision_mask();
- e.collision_layer = e.owner->get_collision_layer();
-
- _enter_grid(&e, p_aabb, e._static, layer_changed);
- }
- if (e.aabb != Rect2()) {
- // Need _exit_grid to remove from cells based on the old layer values.
- e.collision_mask = old_mask;
- e.collision_layer = old_layer;
-
- _exit_grid(&e, e.aabb, e._static, layer_changed);
-
- e.collision_mask = e.owner->get_collision_mask();
- e.collision_layer = e.owner->get_collision_layer();
- }
- e.aabb = p_aabb;
- }
-
- _check_motion(&e);
-}
-
-void BroadPhase2DHashGrid::set_static(ID p_id, bool p_static) {
- Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND(!E);
-
- Element &e = E->get();
-
- if (e._static == p_static) {
- return;
- }
-
- if (e.aabb != Rect2()) {
- _exit_grid(&e, e.aabb, e._static, false);
- }
-
- e._static = p_static;
-
- if (e.aabb != Rect2()) {
- _enter_grid(&e, e.aabb, e._static, false);
- _check_motion(&e);
- }
-}
-
-void BroadPhase2DHashGrid::remove(ID p_id) {
- Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND(!E);
-
- Element &e = E->get();
-
- if (e.aabb != Rect2()) {
- _exit_grid(&e, e.aabb, e._static, false);
- }
-
- element_map.erase(p_id);
-}
-
-CollisionObject2DSW *BroadPhase2DHashGrid::get_object(ID p_id) const {
- const Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND_V(!E, nullptr);
- return E->get().owner;
-}
-
-bool BroadPhase2DHashGrid::is_static(ID p_id) const {
- const Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND_V(!E, false);
- return E->get()._static;
-}
-
-int BroadPhase2DHashGrid::get_subindex(ID p_id) const {
- const Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND_V(!E, -1);
- return E->get().subindex;
-}
-
-template <bool use_aabb, bool use_segment>
-void BroadPhase2DHashGrid::_cull(const Point2i p_cell, const Rect2 &p_aabb, const Point2 &p_from, const Point2 &p_to, CollisionObject2DSW **p_results, int p_max_results, int *p_result_indices, int &index) {
- PosKey pk;
- pk.x = p_cell.x;
- pk.y = p_cell.y;
-
- uint32_t idx = pk.hash() % hash_table_size;
- PosBin *pb = hash_table[idx];
-
- while (pb) {
- if (pb->key == pk) {
- break;
- }
-
- pb = pb->next;
- }
-
- if (!pb) {
- return;
- }
-
- for (Map<Element *, RC>::Element *E = pb->object_set.front(); E; E = E->next()) {
- if (index >= p_max_results) {
- break;
- }
- if (E->key()->pass == pass) {
- continue;
- }
-
- E->key()->pass = pass;
-
- if (use_aabb && !p_aabb.intersects(E->key()->aabb)) {
- continue;
- }
-
- if (use_segment && !E->key()->aabb.intersects_segment(p_from, p_to)) {
- continue;
- }
-
- p_results[index] = E->key()->owner;
- p_result_indices[index] = E->key()->subindex;
- index++;
- }
-
- for (Map<Element *, RC>::Element *E = pb->static_object_set.front(); E; E = E->next()) {
- if (index >= p_max_results) {
- break;
- }
- if (E->key()->pass == pass) {
- continue;
- }
-
- if (use_aabb && !p_aabb.intersects(E->key()->aabb)) {
- continue;
- }
-
- if (use_segment && !E->key()->aabb.intersects_segment(p_from, p_to)) {
- continue;
- }
-
- E->key()->pass = pass;
- p_results[index] = E->key()->owner;
- p_result_indices[index] = E->key()->subindex;
- index++;
- }
-}
-
-int BroadPhase2DHashGrid::cull_segment(const Vector2 &p_from, const Vector2 &p_to, CollisionObject2DSW **p_results, int p_max_results, int *p_result_indices) {
- pass++;
-
- Vector2 dir = (p_to - p_from);
- if (dir == Vector2()) {
- return 0;
- }
- //avoid divisions by zero
- dir.normalize();
- if (dir.x == 0.0) {
- dir.x = 0.000001;
- }
- if (dir.y == 0.0) {
- dir.y = 0.000001;
- }
- Vector2 delta = dir.abs();
-
- delta.x = cell_size / delta.x;
- delta.y = cell_size / delta.y;
-
- Point2i pos = (p_from / cell_size).floor();
- Point2i end = (p_to / cell_size).floor();
-
- Point2i step = Vector2(SGN(dir.x), SGN(dir.y));
-
- Vector2 max;
-
- if (dir.x < 0) {
- max.x = (Math::floor((double)pos.x) * cell_size - p_from.x) / dir.x;
- } else {
- max.x = (Math::floor((double)pos.x + 1) * cell_size - p_from.x) / dir.x;
- }
-
- if (dir.y < 0) {
- max.y = (Math::floor((double)pos.y) * cell_size - p_from.y) / dir.y;
- } else {
- max.y = (Math::floor((double)pos.y + 1) * cell_size - p_from.y) / dir.y;
- }
-
- int cullcount = 0;
- _cull<false, true>(pos, Rect2(), p_from, p_to, p_results, p_max_results, p_result_indices, cullcount);
-
- bool reached_x = false;
- bool reached_y = false;
-
- while (true) {
- if (max.x < max.y) {
- max.x += delta.x;
- pos.x += step.x;
- } else {
- max.y += delta.y;
- pos.y += step.y;
- }
-
- if (step.x > 0) {
- if (pos.x >= end.x) {
- reached_x = true;
- }
- } else if (pos.x <= end.x) {
- reached_x = true;
- }
-
- if (step.y > 0) {
- if (pos.y >= end.y) {
- reached_y = true;
- }
- } else if (pos.y <= end.y) {
- reached_y = true;
- }
-
- _cull<false, true>(pos, Rect2(), p_from, p_to, p_results, p_max_results, p_result_indices, cullcount);
-
- if (reached_x && reached_y) {
- break;
- }
- }
-
- for (Map<Element *, RC>::Element *E = large_elements.front(); E; E = E->next()) {
- if (cullcount >= p_max_results) {
- break;
- }
- if (E->key()->pass == pass) {
- continue;
- }
-
- E->key()->pass = pass;
-
- /*
- if (use_aabb && !p_aabb.intersects(E->key()->aabb))
- continue;
- */
-
- if (!E->key()->aabb.intersects_segment(p_from, p_to)) {
- continue;
- }
-
- p_results[cullcount] = E->key()->owner;
- p_result_indices[cullcount] = E->key()->subindex;
- cullcount++;
- }
-
- return cullcount;
-}
-
-int BroadPhase2DHashGrid::cull_aabb(const Rect2 &p_aabb, CollisionObject2DSW **p_results, int p_max_results, int *p_result_indices) {
- pass++;
-
- Point2i from = (p_aabb.position / cell_size).floor();
- Point2i to = ((p_aabb.position + p_aabb.size) / cell_size).floor();
- int cullcount = 0;
-
- for (int i = from.x; i <= to.x; i++) {
- for (int j = from.y; j <= to.y; j++) {
- _cull<true, false>(Point2i(i, j), p_aabb, Point2(), Point2(), p_results, p_max_results, p_result_indices, cullcount);
- }
- }
-
- for (Map<Element *, RC>::Element *E = large_elements.front(); E; E = E->next()) {
- if (cullcount >= p_max_results) {
- break;
- }
- if (E->key()->pass == pass) {
- continue;
- }
-
- E->key()->pass = pass;
-
- if (!p_aabb.intersects(E->key()->aabb)) {
- continue;
- }
-
- /*
- if (!E->key()->aabb.intersects_segment(p_from,p_to))
- continue;
- */
-
- p_results[cullcount] = E->key()->owner;
- p_result_indices[cullcount] = E->key()->subindex;
- cullcount++;
- }
- return cullcount;
-}
-
-void BroadPhase2DHashGrid::set_pair_callback(PairCallback p_pair_callback, void *p_userdata) {
- pair_callback = p_pair_callback;
- pair_userdata = p_userdata;
-}
-
-void BroadPhase2DHashGrid::set_unpair_callback(UnpairCallback p_unpair_callback, void *p_userdata) {
- unpair_callback = p_unpair_callback;
- unpair_userdata = p_userdata;
-}
-
-void BroadPhase2DHashGrid::update() {
-}
-
-BroadPhase2DSW *BroadPhase2DHashGrid::_create() {
- return memnew(BroadPhase2DHashGrid);
-}
-
-BroadPhase2DHashGrid::BroadPhase2DHashGrid() {
- hash_table_size = GLOBAL_DEF("physics/2d/bp_hash_table_size", 4096);
- ProjectSettings::get_singleton()->set_custom_property_info("physics/2d/bp_hash_table_size", PropertyInfo(Variant::INT, "physics/2d/bp_hash_table_size", PROPERTY_HINT_RANGE, "0,8192,1,or_greater"));
- hash_table_size = Math::larger_prime(hash_table_size);
- hash_table = memnew_arr(PosBin *, hash_table_size);
-
- cell_size = GLOBAL_DEF("physics/2d/cell_size", 128);
- ProjectSettings::get_singleton()->set_custom_property_info("physics/2d/cell_size", PropertyInfo(Variant::INT, "physics/2d/cell_size", PROPERTY_HINT_RANGE, "0,512,1,or_greater"));
-
- large_object_min_surface = GLOBAL_DEF("physics/2d/large_object_surface_threshold_in_cells", 512);
- ProjectSettings::get_singleton()->set_custom_property_info("physics/2d/large_object_surface_threshold_in_cells", PropertyInfo(Variant::INT, "physics/2d/large_object_surface_threshold_in_cells", PROPERTY_HINT_RANGE, "0,1024,1,or_greater"));
-
- for (uint32_t i = 0; i < hash_table_size; i++) {
- hash_table[i] = nullptr;
- }
- pass = 1;
-
- current = 0;
-}
-
-BroadPhase2DHashGrid::~BroadPhase2DHashGrid() {
- for (uint32_t i = 0; i < hash_table_size; i++) {
- while (hash_table[i]) {
- PosBin *pb = hash_table[i];
- hash_table[i] = pb->next;
- memdelete(pb);
- }
- }
-
- memdelete_arr(hash_table);
-}
-
-/* 3D version of voxel traversal:
-
-public IEnumerable<Point3D> GetCellsOnRay(Ray ray, int maxDepth)
-{
- // Implementation is based on:
- // "A Fast Voxel Traversal Algorithm for Ray Tracing"
- // John Amanatides, Andrew Woo
- // http://www.cse.yorku.ca/~amana/research/grid.pdf
- // https://web.archive.org/web/20100616193049/http://www.devmaster.net/articles/raytracing_series/A%20faster%20voxel%20traversal%20algorithm%20for%20ray%20tracing.pdf
-
- // NOTES:
- // * This code assumes that the ray's position and direction are in 'cell coordinates', which means
- // that one unit equals one cell in all directions.
- // * When the ray doesn't start within the voxel grid, calculate the first position at which the
- // ray could enter the grid. If it never enters the grid, there is nothing more to do here.
- // * Also, it is important to test when the ray exits the voxel grid when the grid isn't infinite.
- // * The Point3D structure is a simple structure having three integer fields (X, Y and Z).
-
- // The cell in which the ray starts.
- Point3D start = GetCellAt(ray.Position); // Rounds the position's X, Y and Z down to the nearest integer values.
- int x = start.X;
- int y = start.Y;
- int z = start.Z;
-
- // Determine which way we go.
- int stepX = Math.Sign(ray.Direction.X);
- int stepY = Math.Sign(ray.Direction.Y);
- int stepZ = Math.Sign(ray.Direction.Z);
-
- // Calculate cell boundaries. When the step (i.e. direction sign) is positive,
- // the next boundary is AFTER our current position, meaning that we have to add 1.
- // Otherwise, it is BEFORE our current position, in which case we add nothing.
- Point3D cellBoundary = new Point3D(
- x + (stepX > 0 ? 1 : 0),
- y + (stepY > 0 ? 1 : 0),
- z + (stepZ > 0 ? 1 : 0));
-
- // NOTE: For the following calculations, the result will be Single.PositiveInfinity
- // when ray.Direction.X, Y or Z equals zero, which is OK. However, when the left-hand
- // value of the division also equals zero, the result is Single.NaN, which is not OK.
-
- // Determine how far we can travel along the ray before we hit a voxel boundary.
- Vector3 tMax = new Vector3(
- (cellBoundary.X - ray.Position.X) / ray.Direction.X, // Boundary is a plane on the YZ axis.
- (cellBoundary.Y - ray.Position.Y) / ray.Direction.Y, // Boundary is a plane on the XZ axis.
- (cellBoundary.Z - ray.Position.Z) / ray.Direction.Z); // Boundary is a plane on the XY axis.
- if (Single.IsNaN(tMax.X)) tMax.X = Single.PositiveInfinity;
- if (Single.IsNaN(tMax.Y)) tMax.Y = Single.PositiveInfinity;
- if (Single.IsNaN(tMax.Z)) tMax.Z = Single.PositiveInfinity;
-
- // Determine how far we must travel along the ray before we have crossed a gridcell.
- Vector3 tDelta = new Vector3(
- stepX / ray.Direction.X, // Crossing the width of a cell.
- stepY / ray.Direction.Y, // Crossing the height of a cell.
- stepZ / ray.Direction.Z); // Crossing the depth of a cell.
- if (Single.IsNaN(tDelta.X)) tDelta.X = Single.PositiveInfinity;
- if (Single.IsNaN(tDelta.Y)) tDelta.Y = Single.PositiveInfinity;
- if (Single.IsNaN(tDelta.Z)) tDelta.Z = Single.PositiveInfinity;
-
- // For each step, determine which distance to the next voxel boundary is lowest (i.e.
- // which voxel boundary is nearest) and walk that way.
- for (int i = 0; i < maxDepth; i++)
- {
- // Return it.
- yield return new Point3D(x, y, z);
-
- // Do the next step.
- if (tMax.X < tMax.Y && tMax.X < tMax.Z)
- {
- // tMax.X is the lowest, an YZ cell boundary plane is nearest.
- x += stepX;
- tMax.X += tDelta.X;
- }
- else if (tMax.Y < tMax.Z)
- {
- // tMax.Y is the lowest, an XZ cell boundary plane is nearest.
- y += stepY;
- tMax.Y += tDelta.Y;
- }
- else
- {
- // tMax.Z is the lowest, an XY cell boundary plane is nearest.
- z += stepZ;
- tMax.Z += tDelta.Z;
- }
- }
-
- */
diff --git a/servers/physics_2d/broad_phase_2d_hash_grid.h b/servers/physics_2d/broad_phase_2d_hash_grid.h
deleted file mode 100644
index bb7c03b989..0000000000
--- a/servers/physics_2d/broad_phase_2d_hash_grid.h
+++ /dev/null
@@ -1,194 +0,0 @@
-/*************************************************************************/
-/* broad_phase_2d_hash_grid.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 BROAD_PHASE_2D_HASH_GRID_H
-#define BROAD_PHASE_2D_HASH_GRID_H
-
-#include "broad_phase_2d_sw.h"
-#include "core/templates/map.h"
-
-class BroadPhase2DHashGrid : public BroadPhase2DSW {
- struct PairData {
- bool colliding;
- int rc;
- void *ud;
- PairData() {
- colliding = false;
- rc = 1;
- ud = nullptr;
- }
- };
-
- struct Element {
- ID self;
- CollisionObject2DSW *owner;
- bool _static;
- Rect2 aabb;
- // Owner's collision_mask/layer, used to detect changes in layers.
- uint32_t collision_mask;
- uint32_t collision_layer;
- int subindex;
- uint64_t pass;
- Map<Element *, PairData *> paired;
- };
-
- struct RC {
- int ref;
-
- _FORCE_INLINE_ int inc() {
- ref++;
- return ref;
- }
- _FORCE_INLINE_ int dec() {
- ref--;
- return ref;
- }
-
- _FORCE_INLINE_ RC() {
- ref = 0;
- }
- };
-
- Map<ID, Element> element_map;
- Map<Element *, RC> large_elements;
-
- ID current;
-
- uint64_t pass;
-
- struct PairKey {
- union {
- struct {
- ID a;
- ID b;
- };
- uint64_t key;
- };
-
- _FORCE_INLINE_ bool operator<(const PairKey &p_key) const {
- return key < p_key.key;
- }
-
- PairKey() { key = 0; }
- PairKey(ID p_a, ID p_b) {
- if (p_a > p_b) {
- a = p_b;
- b = p_a;
- } else {
- a = p_a;
- b = p_b;
- }
- }
- };
-
- Map<PairKey, PairData> pair_map;
-
- int cell_size;
- int large_object_min_surface;
-
- PairCallback pair_callback;
- void *pair_userdata;
- UnpairCallback unpair_callback;
- void *unpair_userdata;
-
- static _FORCE_INLINE_ bool _test_collision_mask(uint32_t p_mask1, uint32_t p_layer1, uint32_t p_mask2, uint32_t p_layer2) {
- return p_mask1 & p_layer2 || p_mask2 & p_layer1;
- }
-
- void _enter_grid(Element *p_elem, const Rect2 &p_rect, bool p_static, bool p_force_enter);
- void _exit_grid(Element *p_elem, const Rect2 &p_rect, bool p_static, bool p_force_exit);
- template <bool use_aabb, bool use_segment>
- _FORCE_INLINE_ void _cull(const Point2i p_cell, const Rect2 &p_aabb, const Point2 &p_from, const Point2 &p_to, CollisionObject2DSW **p_results, int p_max_results, int *p_result_indices, int &index);
-
- struct PosKey {
- union {
- struct {
- int32_t x;
- int32_t y;
- };
- uint64_t key;
- };
-
- _FORCE_INLINE_ uint32_t hash() const {
- uint64_t k = key;
- k = (~k) + (k << 18); // k = (k << 18) - k - 1;
- k = k ^ (k >> 31);
- k = k * 21; // k = (k + (k << 2)) + (k << 4);
- k = k ^ (k >> 11);
- k = k + (k << 6);
- k = k ^ (k >> 22);
- return k;
- }
-
- bool operator==(const PosKey &p_key) const { return key == p_key.key; }
- _FORCE_INLINE_ bool operator<(const PosKey &p_key) const {
- return key < p_key.key;
- }
- };
-
- struct PosBin {
- PosKey key;
- Map<Element *, RC> object_set;
- Map<Element *, RC> static_object_set;
- PosBin *next;
- };
-
- uint32_t hash_table_size;
- PosBin **hash_table;
-
- void _pair_attempt(Element *p_elem, Element *p_with);
- void _unpair_attempt(Element *p_elem, Element *p_with);
- void _check_motion(Element *p_elem);
-
-public:
- virtual ID create(CollisionObject2DSW *p_object, int p_subindex = 0);
- virtual void move(ID p_id, const Rect2 &p_aabb);
- virtual void set_static(ID p_id, bool p_static);
- virtual void remove(ID p_id);
-
- virtual CollisionObject2DSW *get_object(ID p_id) const;
- virtual bool is_static(ID p_id) const;
- virtual int get_subindex(ID p_id) const;
-
- virtual int cull_segment(const Vector2 &p_from, const Vector2 &p_to, CollisionObject2DSW **p_results, int p_max_results, int *p_result_indices = nullptr);
- virtual int cull_aabb(const Rect2 &p_aabb, CollisionObject2DSW **p_results, int p_max_results, int *p_result_indices = nullptr);
-
- virtual void set_pair_callback(PairCallback p_pair_callback, void *p_userdata);
- virtual void set_unpair_callback(UnpairCallback p_unpair_callback, void *p_userdata);
-
- virtual void update();
-
- static BroadPhase2DSW *_create();
-
- BroadPhase2DHashGrid();
- ~BroadPhase2DHashGrid();
-};
-
-#endif // BROAD_PHASE_2D_HASH_GRID_H
diff --git a/servers/physics_2d/broad_phase_2d_sw.h b/servers/physics_2d/broad_phase_2d_sw.h
index d17ee6e2d6..0f82f06b9c 100644
--- a/servers/physics_2d/broad_phase_2d_sw.h
+++ b/servers/physics_2d/broad_phase_2d_sw.h
@@ -48,7 +48,7 @@ public:
typedef void (*UnpairCallback)(CollisionObject2DSW *A, int p_subindex_A, CollisionObject2DSW *B, int p_subindex_B, void *p_data, void *p_userdata);
// 0 is an invalid ID
- virtual ID create(CollisionObject2DSW *p_object_, int p_subindex = 0) = 0;
+ virtual ID create(CollisionObject2DSW *p_object_, int p_subindex = 0, const Rect2 &p_aabb = Rect2(), bool p_static = false) = 0;
virtual void move(ID p_id, const Rect2 &p_aabb) = 0;
virtual void set_static(ID p_id, bool p_static) = 0;
virtual void remove(ID p_id) = 0;
diff --git a/servers/physics_2d/collision_object_2d_sw.cpp b/servers/physics_2d/collision_object_2d_sw.cpp
index 7a2f312263..fa87dc1f3f 100644
--- a/servers/physics_2d/collision_object_2d_sw.cpp
+++ b/servers/physics_2d/collision_object_2d_sw.cpp
@@ -182,19 +182,19 @@ void CollisionObject2DSW::_update_shapes() {
continue;
}
- if (s.bpid == 0) {
- s.bpid = space->get_broadphase()->create(this, i);
- space->get_broadphase()->set_static(s.bpid, _static);
- }
-
//not quite correct, should compute the next matrix..
Rect2 shape_aabb = s.shape->get_aabb();
Transform2D xform = transform * s.xform;
shape_aabb = xform.xform(shape_aabb);
+ shape_aabb.grow_by((s.aabb_cache.size.x + s.aabb_cache.size.y) * 0.5 * 0.05);
s.aabb_cache = shape_aabb;
- s.aabb_cache = s.aabb_cache.grow((s.aabb_cache.size.x + s.aabb_cache.size.y) * 0.5 * 0.05);
- space->get_broadphase()->move(s.bpid, s.aabb_cache);
+ if (s.bpid == 0) {
+ s.bpid = space->get_broadphase()->create(this, i, shape_aabb, _static);
+ space->get_broadphase()->set_static(s.bpid, _static);
+ }
+
+ space->get_broadphase()->move(s.bpid, shape_aabb);
}
}
@@ -209,11 +209,6 @@ void CollisionObject2DSW::_update_shapes_with_motion(const Vector2 &p_motion) {
continue;
}
- if (s.bpid == 0) {
- s.bpid = space->get_broadphase()->create(this, i);
- space->get_broadphase()->set_static(s.bpid, _static);
- }
-
//not quite correct, should compute the next matrix..
Rect2 shape_aabb = s.shape->get_aabb();
Transform2D xform = transform * s.xform;
@@ -221,6 +216,11 @@ void CollisionObject2DSW::_update_shapes_with_motion(const Vector2 &p_motion) {
shape_aabb = shape_aabb.merge(Rect2(shape_aabb.position + p_motion, shape_aabb.size)); //use motion
s.aabb_cache = shape_aabb;
+ if (s.bpid == 0) {
+ s.bpid = space->get_broadphase()->create(this, i, shape_aabb, _static);
+ space->get_broadphase()->set_static(s.bpid, _static);
+ }
+
space->get_broadphase()->move(s.bpid, shape_aabb);
}
}
diff --git a/servers/physics_2d/physics_server_2d_sw.cpp b/servers/physics_2d/physics_server_2d_sw.cpp
index 6d64f4126c..425546e5ca 100644
--- a/servers/physics_2d/physics_server_2d_sw.cpp
+++ b/servers/physics_2d/physics_server_2d_sw.cpp
@@ -30,8 +30,7 @@
#include "physics_server_2d_sw.h"
-#include "broad_phase_2d_basic.h"
-#include "broad_phase_2d_hash_grid.h"
+#include "broad_phase_2d_bvh.h"
#include "collision_solver_2d_sw.h"
#include "core/config/project_settings.h"
#include "core/debugger/engine_debugger.h"
@@ -1356,8 +1355,7 @@ PhysicsServer2DSW *PhysicsServer2DSW::singletonsw = nullptr;
PhysicsServer2DSW::PhysicsServer2DSW(bool p_using_threads) {
singletonsw = this;
- BroadPhase2DSW::create_func = BroadPhase2DHashGrid::_create;
- //BroadPhase2DSW::create_func=BroadPhase2DBasic::_create;
+ BroadPhase2DSW::create_func = BroadPhase2DBVH::_create;
active = true;
island_count = 0;
diff --git a/servers/physics_3d/broad_phase_3d_basic.cpp b/servers/physics_3d/broad_phase_3d_basic.cpp
deleted file mode 100644
index b41c2530da..0000000000
--- a/servers/physics_3d/broad_phase_3d_basic.cpp
+++ /dev/null
@@ -1,212 +0,0 @@
-/*************************************************************************/
-/* broad_phase_3d_basic.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 "broad_phase_3d_basic.h"
-#include "core/string/print_string.h"
-#include "core/templates/list.h"
-
-BroadPhase3DSW::ID BroadPhase3DBasic::create(CollisionObject3DSW *p_object, int p_subindex) {
- ERR_FAIL_COND_V(p_object == nullptr, 0);
-
- current++;
-
- Element e;
- e.owner = p_object;
- e._static = false;
- e.subindex = p_subindex;
-
- element_map[current] = e;
- return current;
-}
-
-void BroadPhase3DBasic::move(ID p_id, const AABB &p_aabb) {
- Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND(!E);
- E->get().aabb = p_aabb;
-}
-
-void BroadPhase3DBasic::set_static(ID p_id, bool p_static) {
- Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND(!E);
- E->get()._static = p_static;
-}
-
-void BroadPhase3DBasic::remove(ID p_id) {
- Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND(!E);
- List<PairKey> to_erase;
- //unpair must be done immediately on removal to avoid potential invalid pointers
- for (Map<PairKey, void *>::Element *F = pair_map.front(); F; F = F->next()) {
- if (F->key().a == p_id || F->key().b == p_id) {
- if (unpair_callback) {
- Element *elem_A = &element_map[F->key().a];
- Element *elem_B = &element_map[F->key().b];
- unpair_callback(elem_A->owner, elem_A->subindex, elem_B->owner, elem_B->subindex, F->get(), unpair_userdata);
- }
- to_erase.push_back(F->key());
- }
- }
- while (to_erase.size()) {
- pair_map.erase(to_erase.front()->get());
- to_erase.pop_front();
- }
- element_map.erase(E);
-}
-
-CollisionObject3DSW *BroadPhase3DBasic::get_object(ID p_id) const {
- const Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND_V(!E, nullptr);
- return E->get().owner;
-}
-
-bool BroadPhase3DBasic::is_static(ID p_id) const {
- const Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND_V(!E, false);
- return E->get()._static;
-}
-
-int BroadPhase3DBasic::get_subindex(ID p_id) const {
- const Map<ID, Element>::Element *E = element_map.find(p_id);
- ERR_FAIL_COND_V(!E, -1);
- return E->get().subindex;
-}
-
-int BroadPhase3DBasic::cull_point(const Vector3 &p_point, CollisionObject3DSW **p_results, int p_max_results, int *p_result_indices) {
- int rc = 0;
-
- for (Map<ID, Element>::Element *E = element_map.front(); E; E = E->next()) {
- const AABB aabb = E->get().aabb;
- if (aabb.has_point(p_point)) {
- p_results[rc] = E->get().owner;
- p_result_indices[rc] = E->get().subindex;
- rc++;
- if (rc >= p_max_results) {
- break;
- }
- }
- }
-
- return rc;
-}
-
-int BroadPhase3DBasic::cull_segment(const Vector3 &p_from, const Vector3 &p_to, CollisionObject3DSW **p_results, int p_max_results, int *p_result_indices) {
- int rc = 0;
-
- for (Map<ID, Element>::Element *E = element_map.front(); E; E = E->next()) {
- const AABB aabb = E->get().aabb;
- if (aabb.intersects_segment(p_from, p_to)) {
- p_results[rc] = E->get().owner;
- p_result_indices[rc] = E->get().subindex;
- rc++;
- if (rc >= p_max_results) {
- break;
- }
- }
- }
-
- return rc;
-}
-
-int BroadPhase3DBasic::cull_aabb(const AABB &p_aabb, CollisionObject3DSW **p_results, int p_max_results, int *p_result_indices) {
- int rc = 0;
-
- for (Map<ID, Element>::Element *E = element_map.front(); E; E = E->next()) {
- const AABB aabb = E->get().aabb;
- if (aabb.intersects(p_aabb)) {
- p_results[rc] = E->get().owner;
- p_result_indices[rc] = E->get().subindex;
- rc++;
- if (rc >= p_max_results) {
- break;
- }
- }
- }
-
- return rc;
-}
-
-void BroadPhase3DBasic::set_pair_callback(PairCallback p_pair_callback, void *p_userdata) {
- pair_userdata = p_userdata;
- pair_callback = p_pair_callback;
-}
-
-void BroadPhase3DBasic::set_unpair_callback(UnpairCallback p_unpair_callback, void *p_userdata) {
- unpair_userdata = p_userdata;
- unpair_callback = p_unpair_callback;
-}
-
-void BroadPhase3DBasic::update() {
- // recompute pairs
- for (Map<ID, Element>::Element *I = element_map.front(); I; I = I->next()) {
- for (Map<ID, Element>::Element *J = I->next(); J; J = J->next()) {
- Element *elem_A = &I->get();
- Element *elem_B = &J->get();
-
- if (elem_A->owner == elem_B->owner) {
- continue;
- }
-
- bool pair_ok = elem_A->aabb.intersects(elem_B->aabb) && (!elem_A->_static || !elem_B->_static);
-
- PairKey key(I->key(), J->key());
-
- Map<PairKey, void *>::Element *E = pair_map.find(key);
-
- if (!pair_ok && E) {
- if (unpair_callback) {
- unpair_callback(elem_A->owner, elem_A->subindex, elem_B->owner, elem_B->subindex, E->get(), unpair_userdata);
- }
- pair_map.erase(key);
- }
-
- if (pair_ok && !E) {
- void *data = nullptr;
- if (pair_callback) {
- data = pair_callback(elem_A->owner, elem_A->subindex, elem_B->owner, elem_B->subindex, unpair_userdata);
- if (data) {
- pair_map.insert(key, data);
- }
- }
- }
- }
- }
-}
-
-BroadPhase3DSW *BroadPhase3DBasic::_create() {
- return memnew(BroadPhase3DBasic);
-}
-
-BroadPhase3DBasic::BroadPhase3DBasic() {
- current = 1;
- unpair_callback = nullptr;
- unpair_userdata = nullptr;
- pair_callback = nullptr;
- pair_userdata = nullptr;
-}
diff --git a/servers/physics_3d/broad_phase_octree.cpp b/servers/physics_3d/broad_phase_3d_bvh.cpp
index 11324fa4e4..f9f64f786d 100644
--- a/servers/physics_3d/broad_phase_octree.cpp
+++ b/servers/physics_3d/broad_phase_3d_bvh.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* broad_phase_octree.cpp */
+/* broad_phase_3d_bvh.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,55 +28,55 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "broad_phase_octree.h"
+#include "broad_phase_3d_bvh.h"
#include "collision_object_3d_sw.h"
-BroadPhase3DSW::ID BroadPhaseOctree::create(CollisionObject3DSW *p_object, int p_subindex) {
- ID oid = octree.create(p_object, AABB(), p_subindex, false, 1 << p_object->get_type(), 0);
- return oid;
+BroadPhase3DBVH::ID BroadPhase3DBVH::create(CollisionObject3DSW *p_object, int p_subindex, const AABB &p_aabb, bool p_static) {
+ ID oid = bvh.create(p_object, true, p_aabb, p_subindex, !p_static, 1 << p_object->get_type(), p_static ? 0 : 0xFFFFF); // Pair everything, don't care?
+ return oid + 1;
}
-void BroadPhaseOctree::move(ID p_id, const AABB &p_aabb) {
- octree.move(p_id, p_aabb);
+void BroadPhase3DBVH::move(ID p_id, const AABB &p_aabb) {
+ bvh.move(p_id - 1, p_aabb);
}
-void BroadPhaseOctree::set_static(ID p_id, bool p_static) {
- CollisionObject3DSW *it = octree.get(p_id);
- octree.set_pairable(p_id, !p_static, 1 << it->get_type(), p_static ? 0 : 0xFFFFF); //pair everything, don't care 1?
+void BroadPhase3DBVH::set_static(ID p_id, bool p_static) {
+ CollisionObject3DSW *it = bvh.get(p_id - 1);
+ bvh.set_pairable(p_id - 1, !p_static, 1 << it->get_type(), p_static ? 0 : 0xFFFFF, false); // Pair everything, don't care?
}
-void BroadPhaseOctree::remove(ID p_id) {
- octree.erase(p_id);
+void BroadPhase3DBVH::remove(ID p_id) {
+ bvh.erase(p_id - 1);
}
-CollisionObject3DSW *BroadPhaseOctree::get_object(ID p_id) const {
- CollisionObject3DSW *it = octree.get(p_id);
+CollisionObject3DSW *BroadPhase3DBVH::get_object(ID p_id) const {
+ CollisionObject3DSW *it = bvh.get(p_id - 1);
ERR_FAIL_COND_V(!it, nullptr);
return it;
}
-bool BroadPhaseOctree::is_static(ID p_id) const {
- return !octree.is_pairable(p_id);
+bool BroadPhase3DBVH::is_static(ID p_id) const {
+ return !bvh.is_pairable(p_id - 1);
}
-int BroadPhaseOctree::get_subindex(ID p_id) const {
- return octree.get_subindex(p_id);
+int BroadPhase3DBVH::get_subindex(ID p_id) const {
+ return bvh.get_subindex(p_id - 1);
}
-int BroadPhaseOctree::cull_point(const Vector3 &p_point, CollisionObject3DSW **p_results, int p_max_results, int *p_result_indices) {
- return octree.cull_point(p_point, p_results, p_max_results, p_result_indices);
+int BroadPhase3DBVH::cull_point(const Vector3 &p_point, CollisionObject3DSW **p_results, int p_max_results, int *p_result_indices) {
+ return bvh.cull_point(p_point, p_results, p_max_results, p_result_indices);
}
-int BroadPhaseOctree::cull_segment(const Vector3 &p_from, const Vector3 &p_to, CollisionObject3DSW **p_results, int p_max_results, int *p_result_indices) {
- return octree.cull_segment(p_from, p_to, p_results, p_max_results, p_result_indices);
+int BroadPhase3DBVH::cull_segment(const Vector3 &p_from, const Vector3 &p_to, CollisionObject3DSW **p_results, int p_max_results, int *p_result_indices) {
+ return bvh.cull_segment(p_from, p_to, p_results, p_max_results, p_result_indices);
}
-int BroadPhaseOctree::cull_aabb(const AABB &p_aabb, CollisionObject3DSW **p_results, int p_max_results, int *p_result_indices) {
- return octree.cull_aabb(p_aabb, p_results, p_max_results, p_result_indices);
+int BroadPhase3DBVH::cull_aabb(const AABB &p_aabb, CollisionObject3DSW **p_results, int p_max_results, int *p_result_indices) {
+ return bvh.cull_aabb(p_aabb, p_results, p_max_results, p_result_indices);
}
-void *BroadPhaseOctree::_pair_callback(void *self, OctreeElementID p_A, CollisionObject3DSW *p_object_A, int subindex_A, OctreeElementID p_B, CollisionObject3DSW *p_object_B, int subindex_B) {
- BroadPhaseOctree *bpo = (BroadPhaseOctree *)(self);
+void *BroadPhase3DBVH::_pair_callback(void *self, uint32_t p_A, CollisionObject3DSW *p_object_A, int subindex_A, uint32_t p_B, CollisionObject3DSW *p_object_B, int subindex_B) {
+ BroadPhase3DBVH *bpo = (BroadPhase3DBVH *)(self);
if (!bpo->pair_callback) {
return nullptr;
}
@@ -84,8 +84,8 @@ void *BroadPhaseOctree::_pair_callback(void *self, OctreeElementID p_A, Collisio
return bpo->pair_callback(p_object_A, subindex_A, p_object_B, subindex_B, bpo->pair_userdata);
}
-void BroadPhaseOctree::_unpair_callback(void *self, OctreeElementID p_A, CollisionObject3DSW *p_object_A, int subindex_A, OctreeElementID p_B, CollisionObject3DSW *p_object_B, int subindex_B, void *pairdata) {
- BroadPhaseOctree *bpo = (BroadPhaseOctree *)(self);
+void BroadPhase3DBVH::_unpair_callback(void *self, uint32_t p_A, CollisionObject3DSW *p_object_A, int subindex_A, uint32_t p_B, CollisionObject3DSW *p_object_B, int subindex_B, void *pairdata) {
+ BroadPhase3DBVH *bpo = (BroadPhase3DBVH *)(self);
if (!bpo->unpair_callback) {
return;
}
@@ -93,27 +93,27 @@ void BroadPhaseOctree::_unpair_callback(void *self, OctreeElementID p_A, Collisi
bpo->unpair_callback(p_object_A, subindex_A, p_object_B, subindex_B, pairdata, bpo->unpair_userdata);
}
-void BroadPhaseOctree::set_pair_callback(PairCallback p_pair_callback, void *p_userdata) {
+void BroadPhase3DBVH::set_pair_callback(PairCallback p_pair_callback, void *p_userdata) {
pair_callback = p_pair_callback;
pair_userdata = p_userdata;
}
-void BroadPhaseOctree::set_unpair_callback(UnpairCallback p_unpair_callback, void *p_userdata) {
+void BroadPhase3DBVH::set_unpair_callback(UnpairCallback p_unpair_callback, void *p_userdata) {
unpair_callback = p_unpair_callback;
unpair_userdata = p_userdata;
}
-void BroadPhaseOctree::update() {
- // does.. not?
+void BroadPhase3DBVH::update() {
+ bvh.update();
}
-BroadPhase3DSW *BroadPhaseOctree::_create() {
- return memnew(BroadPhaseOctree);
+BroadPhase3DSW *BroadPhase3DBVH::_create() {
+ return memnew(BroadPhase3DBVH);
}
-BroadPhaseOctree::BroadPhaseOctree() {
- octree.set_pair_callback(_pair_callback, this);
- octree.set_unpair_callback(_unpair_callback, this);
+BroadPhase3DBVH::BroadPhase3DBVH() {
+ bvh.set_pair_callback(_pair_callback, this);
+ bvh.set_unpair_callback(_unpair_callback, this);
pair_callback = nullptr;
pair_userdata = nullptr;
unpair_userdata = nullptr;
diff --git a/servers/physics_3d/broad_phase_octree.h b/servers/physics_3d/broad_phase_3d_bvh.h
index ee681dda96..30b8b7f2aa 100644
--- a/servers/physics_3d/broad_phase_octree.h
+++ b/servers/physics_3d/broad_phase_3d_bvh.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* broad_phase_octree.h */
+/* broad_phase_3d_bvh.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,17 +28,17 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef BROAD_PHASE_OCTREE_H
-#define BROAD_PHASE_OCTREE_H
+#ifndef BROAD_PHASE_3D_BVH_H
+#define BROAD_PHASE_3D_BVH_H
#include "broad_phase_3d_sw.h"
-#include "core/math/octree.h"
+#include "core/math/bvh.h"
-class BroadPhaseOctree : public BroadPhase3DSW {
- Octree<CollisionObject3DSW, true> octree;
+class BroadPhase3DBVH : public BroadPhase3DSW {
+ BVH_Manager<CollisionObject3DSW, true, 128> bvh;
- static void *_pair_callback(void *, OctreeElementID, CollisionObject3DSW *, int, OctreeElementID, CollisionObject3DSW *, int);
- static void _unpair_callback(void *, OctreeElementID, CollisionObject3DSW *, int, OctreeElementID, CollisionObject3DSW *, int, void *);
+ static void *_pair_callback(void *, uint32_t, CollisionObject3DSW *, int, uint32_t, CollisionObject3DSW *, int);
+ static void _unpair_callback(void *, uint32_t, CollisionObject3DSW *, int, uint32_t, CollisionObject3DSW *, int, void *);
PairCallback pair_callback;
void *pair_userdata;
@@ -47,7 +47,7 @@ class BroadPhaseOctree : public BroadPhase3DSW {
public:
// 0 is an invalid ID
- virtual ID create(CollisionObject3DSW *p_object, int p_subindex = 0);
+ virtual ID create(CollisionObject3DSW *p_object, int p_subindex = 0, const AABB &p_aabb = AABB(), bool p_static = false);
virtual void move(ID p_id, const AABB &p_aabb);
virtual void set_static(ID p_id, bool p_static);
virtual void remove(ID p_id);
@@ -66,7 +66,7 @@ public:
virtual void update();
static BroadPhase3DSW *_create();
- BroadPhaseOctree();
+ BroadPhase3DBVH();
};
-#endif // BROAD_PHASE_OCTREE_H
+#endif // BROAD_PHASE_3D_BVH_H
diff --git a/servers/physics_3d/broad_phase_3d_sw.h b/servers/physics_3d/broad_phase_3d_sw.h
index 283c087b96..98313cb216 100644
--- a/servers/physics_3d/broad_phase_3d_sw.h
+++ b/servers/physics_3d/broad_phase_3d_sw.h
@@ -48,7 +48,7 @@ public:
typedef void (*UnpairCallback)(CollisionObject3DSW *A, int p_subindex_A, CollisionObject3DSW *B, int p_subindex_B, void *p_data, void *p_userdata);
// 0 is an invalid ID
- virtual ID create(CollisionObject3DSW *p_object_, int p_subindex = 0) = 0;
+ virtual ID create(CollisionObject3DSW *p_object_, int p_subindex = 0, const AABB &p_aabb = AABB(), bool p_static = false) = 0;
virtual void move(ID p_id, const AABB &p_aabb) = 0;
virtual void set_static(ID p_id, bool p_static) = 0;
virtual void remove(ID p_id) = 0;
diff --git a/servers/physics_3d/collision_object_3d_sw.cpp b/servers/physics_3d/collision_object_3d_sw.cpp
index 293a7e6606..459deb1356 100644
--- a/servers/physics_3d/collision_object_3d_sw.cpp
+++ b/servers/physics_3d/collision_object_3d_sw.cpp
@@ -146,22 +146,23 @@ void CollisionObject3DSW::_update_shapes() {
for (int i = 0; i < shapes.size(); i++) {
Shape &s = shapes.write[i];
- if (s.bpid == 0) {
- s.bpid = space->get_broadphase()->create(this, i);
- space->get_broadphase()->set_static(s.bpid, _static);
- }
//not quite correct, should compute the next matrix..
AABB shape_aabb = s.shape->get_aabb();
Transform xform = transform * s.xform;
shape_aabb = xform.xform(shape_aabb);
+ shape_aabb.grow_by((s.aabb_cache.size.x + s.aabb_cache.size.y) * 0.5 * 0.05);
s.aabb_cache = shape_aabb;
- s.aabb_cache = s.aabb_cache.grow((s.aabb_cache.size.x + s.aabb_cache.size.y) * 0.5 * 0.05);
Vector3 scale = xform.get_basis().get_scale();
s.area_cache = s.shape->get_area() * scale.x * scale.y * scale.z;
- space->get_broadphase()->move(s.bpid, s.aabb_cache);
+ if (s.bpid == 0) {
+ s.bpid = space->get_broadphase()->create(this, i, shape_aabb, _static);
+ space->get_broadphase()->set_static(s.bpid, _static);
+ }
+
+ space->get_broadphase()->move(s.bpid, shape_aabb);
}
}
@@ -172,18 +173,19 @@ void CollisionObject3DSW::_update_shapes_with_motion(const Vector3 &p_motion) {
for (int i = 0; i < shapes.size(); i++) {
Shape &s = shapes.write[i];
- if (s.bpid == 0) {
- s.bpid = space->get_broadphase()->create(this, i);
- space->get_broadphase()->set_static(s.bpid, _static);
- }
//not quite correct, should compute the next matrix..
AABB shape_aabb = s.shape->get_aabb();
Transform xform = transform * s.xform;
shape_aabb = xform.xform(shape_aabb);
- shape_aabb = shape_aabb.merge(AABB(shape_aabb.position + p_motion, shape_aabb.size)); //use motion
+ shape_aabb.merge_with(AABB(shape_aabb.position + p_motion, shape_aabb.size)); //use motion
s.aabb_cache = shape_aabb;
+ if (s.bpid == 0) {
+ s.bpid = space->get_broadphase()->create(this, i, shape_aabb, _static);
+ space->get_broadphase()->set_static(s.bpid, _static);
+ }
+
space->get_broadphase()->move(s.bpid, shape_aabb);
}
}
diff --git a/servers/physics_3d/physics_server_3d_sw.cpp b/servers/physics_3d/physics_server_3d_sw.cpp
index c08e2b5794..f3eb1ae48f 100644
--- a/servers/physics_3d/physics_server_3d_sw.cpp
+++ b/servers/physics_3d/physics_server_3d_sw.cpp
@@ -30,8 +30,7 @@
#include "physics_server_3d_sw.h"
-#include "broad_phase_3d_basic.h"
-#include "broad_phase_octree.h"
+#include "broad_phase_3d_bvh.h"
#include "core/debugger/engine_debugger.h"
#include "core/os/os.h"
#include "joints/cone_twist_joint_3d_sw.h"
@@ -1755,7 +1754,8 @@ void PhysicsServer3DSW::_shape_col_cbk(const Vector3 &p_point_A, int p_index_A,
PhysicsServer3DSW *PhysicsServer3DSW::singletonsw = nullptr;
PhysicsServer3DSW::PhysicsServer3DSW(bool p_using_threads) {
singletonsw = this;
- BroadPhase3DSW::create_func = BroadPhaseOctree::_create;
+ BroadPhase3DSW::create_func = BroadPhase3DBVH::_create;
+
island_count = 0;
active_objects = 0;
collision_pairs = 0;
diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp
index 7f3fc2f8f4..67003a6f64 100644
--- a/servers/rendering/renderer_canvas_cull.cpp
+++ b/servers/rendering/renderer_canvas_cull.cpp
@@ -923,10 +923,15 @@ void RendererCanvasCull::canvas_item_add_set_transform(RID p_item, const Transfo
void RendererCanvasCull::canvas_item_add_mesh(RID p_item, const RID &p_mesh, const Transform2D &p_transform, const Color &p_modulate, RID p_texture) {
Item *canvas_item = canvas_item_owner.getornull(p_item);
ERR_FAIL_COND(!canvas_item);
+ ERR_FAIL_COND(!p_mesh.is_valid());
Item::CommandMesh *m = canvas_item->alloc_command<Item::CommandMesh>();
ERR_FAIL_COND(!m);
m->mesh = p_mesh;
+ if (canvas_item->skeleton.is_valid()) {
+ m->mesh_instance = RSG::storage->mesh_instance_create(p_mesh);
+ RSG::storage->mesh_instance_set_skeleton(m->mesh_instance, canvas_item->skeleton);
+ }
m->texture = p_texture;
@@ -996,8 +1001,30 @@ void RendererCanvasCull::canvas_item_set_z_as_relative_to_parent(RID p_item, boo
void RendererCanvasCull::canvas_item_attach_skeleton(RID p_item, RID p_skeleton) {
Item *canvas_item = canvas_item_owner.getornull(p_item);
ERR_FAIL_COND(!canvas_item);
-
+ if (canvas_item->skeleton == p_skeleton) {
+ return;
+ }
canvas_item->skeleton = p_skeleton;
+
+ Item::Command *c = canvas_item->commands;
+
+ while (c) {
+ if (c->type == Item::Command::TYPE_MESH) {
+ Item::CommandMesh *cm = static_cast<Item::CommandMesh *>(c);
+ if (canvas_item->skeleton.is_valid()) {
+ if (cm->mesh_instance.is_null()) {
+ cm->mesh_instance = RSG::storage->mesh_instance_create(cm->mesh);
+ }
+ RSG::storage->mesh_instance_set_skeleton(cm->mesh_instance, canvas_item->skeleton);
+ } else {
+ if (cm->mesh_instance.is_valid()) {
+ RSG::storage->free(cm->mesh_instance);
+ cm->mesh_instance = RID();
+ }
+ }
+ }
+ c = c->next;
+ }
}
void RendererCanvasCull::canvas_item_set_copy_to_backbuffer(RID p_item, bool p_enable, const Rect2 &p_rect) {
diff --git a/servers/rendering/renderer_canvas_render.h b/servers/rendering/renderer_canvas_render.h
index f08986b021..0e9ef616cb 100644
--- a/servers/rendering/renderer_canvas_render.h
+++ b/servers/rendering/renderer_canvas_render.h
@@ -246,10 +246,16 @@ public:
RID mesh;
Transform2D transform;
Color modulate;
+ RID mesh_instance;
RID texture;
CommandMesh() { type = TYPE_MESH; }
+ ~CommandMesh() {
+ if (mesh_instance.is_valid()) {
+ RendererStorage::base_singleton->free(mesh_instance);
+ }
+ }
};
struct CommandMultiMesh : public Command {
@@ -262,7 +268,6 @@ public:
struct CommandParticles : public Command {
RID particles;
-
RID texture;
CommandParticles() { type = TYPE_PARTICLES; }
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
index aadb7bac19..16c6273ff6 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -536,21 +536,21 @@ void RenderForwardClustered::_render_list_with_threads(RenderListParameters *p_p
}
}
-void RenderForwardClustered::_setup_environment(RID p_environment, RID p_render_buffers, const CameraMatrix &p_cam_projection, const Transform &p_cam_transform, RID p_reflection_probe, bool p_no_fog, const Size2i &p_screen_size, uint32_t p_cluster_size, uint32_t p_max_cluster_elements, RID p_shadow_atlas, bool p_flip_y, const Color &p_default_bg_color, float p_znear, float p_zfar, bool p_opaque_render_buffers, bool p_pancake_shadows, int p_index) {
- //CameraMatrix projection = p_cam_projection;
+void RenderForwardClustered::_setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_opaque_render_buffers, bool p_pancake_shadows, int p_index) {
+ //CameraMatrix projection = p_render_data->cam_projection;
//projection.flip_y(); // Vulkan and modern APIs use Y-Down
CameraMatrix correction;
correction.set_depth_correction(p_flip_y);
- CameraMatrix projection = correction * p_cam_projection;
+ CameraMatrix projection = correction * p_render_data->cam_projection;
//store camera into ubo
RendererStorageRD::store_camera(projection, scene_state.ubo.projection_matrix);
RendererStorageRD::store_camera(projection.inverse(), scene_state.ubo.inv_projection_matrix);
- RendererStorageRD::store_transform(p_cam_transform, scene_state.ubo.camera_matrix);
- RendererStorageRD::store_transform(p_cam_transform.affine_inverse(), scene_state.ubo.inv_camera_matrix);
+ RendererStorageRD::store_transform(p_render_data->cam_transform, scene_state.ubo.camera_matrix);
+ RendererStorageRD::store_transform(p_render_data->cam_transform.affine_inverse(), scene_state.ubo.inv_camera_matrix);
- scene_state.ubo.z_far = p_zfar;
- scene_state.ubo.z_near = p_znear;
+ scene_state.ubo.z_far = p_render_data->z_far;
+ scene_state.ubo.z_near = p_render_data->z_near;
scene_state.ubo.pancake_shadows = p_pancake_shadows;
@@ -568,17 +568,17 @@ void RenderForwardClustered::_setup_environment(RID p_environment, RID p_render_
scene_state.ubo.screen_pixel_size[0] = screen_pixel_size.x;
scene_state.ubo.screen_pixel_size[1] = screen_pixel_size.y;
- scene_state.ubo.cluster_shift = get_shift_from_power_of_2(p_cluster_size);
- scene_state.ubo.max_cluster_element_count_div_32 = p_max_cluster_elements / 32;
+ scene_state.ubo.cluster_shift = get_shift_from_power_of_2(p_render_data->cluster_size);
+ scene_state.ubo.max_cluster_element_count_div_32 = p_render_data->cluster_max_elements / 32;
{
- uint32_t cluster_screen_width = (p_screen_size.width - 1) / p_cluster_size + 1;
- uint32_t cluster_screen_height = (p_screen_size.height - 1) / p_cluster_size + 1;
+ uint32_t cluster_screen_width = (p_screen_size.width - 1) / p_render_data->cluster_size + 1;
+ uint32_t cluster_screen_height = (p_screen_size.height - 1) / p_render_data->cluster_size + 1;
scene_state.ubo.cluster_type_size = cluster_screen_width * cluster_screen_height * (scene_state.ubo.max_cluster_element_count_div_32 + 32);
scene_state.ubo.cluster_width = cluster_screen_width;
}
- if (p_shadow_atlas.is_valid()) {
- Vector2 sas = shadow_atlas_get_size(p_shadow_atlas);
+ if (p_render_data->shadow_atlas.is_valid()) {
+ Vector2 sas = shadow_atlas_get_size(p_render_data->shadow_atlas);
scene_state.ubo.shadow_atlas_pixel_size[0] = 1.0 / sas.x;
scene_state.ubo.shadow_atlas_pixel_size[1] = 1.0 / sas.y;
}
@@ -594,22 +594,22 @@ void RenderForwardClustered::_setup_environment(RID p_environment, RID p_render_
scene_state.ubo.volumetric_fog_enabled = false;
scene_state.ubo.fog_enabled = false;
- if (p_render_buffers.is_valid()) {
- RenderBufferDataForwardClustered *render_buffers = (RenderBufferDataForwardClustered *)render_buffers_get_data(p_render_buffers);
+ if (p_render_data->render_buffers.is_valid()) {
+ RenderBufferDataForwardClustered *render_buffers = (RenderBufferDataForwardClustered *)render_buffers_get_data(p_render_data->render_buffers);
if (render_buffers->msaa != RS::VIEWPORT_MSAA_DISABLED) {
scene_state.ubo.gi_upscale_for_msaa = true;
}
- if (render_buffers_has_volumetric_fog(p_render_buffers)) {
+ if (render_buffers_has_volumetric_fog(p_render_data->render_buffers)) {
scene_state.ubo.volumetric_fog_enabled = true;
- float fog_end = render_buffers_get_volumetric_fog_end(p_render_buffers);
+ float fog_end = render_buffers_get_volumetric_fog_end(p_render_data->render_buffers);
if (fog_end > 0.0) {
scene_state.ubo.volumetric_fog_inv_length = 1.0 / fog_end;
} else {
scene_state.ubo.volumetric_fog_inv_length = 1.0;
}
- float fog_detail_spread = render_buffers_get_volumetric_fog_detail_spread(p_render_buffers); //reverse lookup
+ float fog_detail_spread = render_buffers_get_volumetric_fog_detail_spread(p_render_data->render_buffers); //reverse lookup
if (fog_detail_spread > 0.0) {
scene_state.ubo.volumetric_fog_detail_spread = 1.0 / fog_detail_spread;
} else {
@@ -618,26 +618,26 @@ void RenderForwardClustered::_setup_environment(RID p_environment, RID p_render_
}
}
#if 0
- if (p_render_buffers.is_valid() && render_buffers_is_sdfgi_enabled(p_render_buffers)) {
- scene_state.ubo.sdfgi_cascade_count = render_buffers_get_sdfgi_cascade_count(p_render_buffers);
- scene_state.ubo.sdfgi_probe_axis_size = render_buffers_get_sdfgi_cascade_probe_count(p_render_buffers);
+ if (p_render_data->render_buffers.is_valid() && render_buffers_is_sdfgi_enabled(p_render_data->render_buffers)) {
+ scene_state.ubo.sdfgi_cascade_count = render_buffers_get_sdfgi_cascade_count(p_render_data->render_buffers);
+ scene_state.ubo.sdfgi_probe_axis_size = render_buffers_get_sdfgi_cascade_probe_count(p_render_data->render_buffers);
scene_state.ubo.sdfgi_cascade_probe_size[0] = scene_state.ubo.sdfgi_probe_axis_size - 1; //float version for performance
scene_state.ubo.sdfgi_cascade_probe_size[1] = scene_state.ubo.sdfgi_probe_axis_size - 1;
scene_state.ubo.sdfgi_cascade_probe_size[2] = scene_state.ubo.sdfgi_probe_axis_size - 1;
- float csize = render_buffers_get_sdfgi_cascade_size(p_render_buffers);
+ float csize = render_buffers_get_sdfgi_cascade_size(p_render_data->render_buffers);
scene_state.ubo.sdfgi_probe_to_uvw = 1.0 / float(scene_state.ubo.sdfgi_cascade_probe_size[0]);
float occ_bias = 0.0;
scene_state.ubo.sdfgi_occlusion_bias = occ_bias / csize;
- scene_state.ubo.sdfgi_use_occlusion = render_buffers_is_sdfgi_using_occlusion(p_render_buffers);
- scene_state.ubo.sdfgi_energy = render_buffers_get_sdfgi_energy(p_render_buffers);
+ scene_state.ubo.sdfgi_use_occlusion = render_buffers_is_sdfgi_using_occlusion(p_render_data->render_buffers);
+ scene_state.ubo.sdfgi_energy = render_buffers_get_sdfgi_energy(p_render_data->render_buffers);
float cascade_voxel_size = (csize / scene_state.ubo.sdfgi_cascade_probe_size[0]);
float occlusion_clamp = (cascade_voxel_size - 0.5) / cascade_voxel_size;
scene_state.ubo.sdfgi_occlusion_clamp[0] = occlusion_clamp;
scene_state.ubo.sdfgi_occlusion_clamp[1] = occlusion_clamp;
scene_state.ubo.sdfgi_occlusion_clamp[2] = occlusion_clamp;
- scene_state.ubo.sdfgi_normal_bias = (render_buffers_get_sdfgi_normal_bias(p_render_buffers) / csize) * scene_state.ubo.sdfgi_cascade_probe_size[0];
+ scene_state.ubo.sdfgi_normal_bias = (render_buffers_get_sdfgi_normal_bias(p_render_data->render_buffers) / csize) * scene_state.ubo.sdfgi_cascade_probe_size[0];
//vec2 tex_pixel_size = 1.0 / vec2(ivec2( (OCT_SIZE+2) * params.probe_axis_size * params.probe_axis_size, (OCT_SIZE+2) * params.probe_axis_size ) );
//vec3 probe_uv_offset = (ivec3(OCT_SIZE+2,OCT_SIZE+2,(OCT_SIZE+2) * params.probe_axis_size)) * tex_pixel_size.xyx;
@@ -658,14 +658,14 @@ void RenderForwardClustered::_setup_environment(RID p_environment, RID p_render_
for (uint32_t i = 0; i < scene_state.ubo.sdfgi_cascade_count; i++) {
SceneState::UBO::SDFGICascade &c = scene_state.ubo.sdfgi_cascades[i];
- Vector3 pos = render_buffers_get_sdfgi_cascade_offset(p_render_buffers, i);
- pos -= p_cam_transform.origin; //make pos local to camera, to reduce numerical error
+ Vector3 pos = render_buffers_get_sdfgi_cascade_offset(p_render_data->render_buffers, i);
+ pos -= p_render_data->cam_transform.origin; //make pos local to camera, to reduce numerical error
c.position[0] = pos.x;
c.position[1] = pos.y;
c.position[2] = pos.z;
- c.to_probe = 1.0 / render_buffers_get_sdfgi_cascade_probe_size(p_render_buffers, i);
+ c.to_probe = 1.0 / render_buffers_get_sdfgi_cascade_probe_size(p_render_data->render_buffers, i);
- Vector3i probe_ofs = render_buffers_get_sdfgi_cascade_probe_offset(p_render_buffers, i);
+ Vector3i probe_ofs = render_buffers_get_sdfgi_cascade_probe_offset(p_render_data->render_buffers, i);
c.probe_world_offset[0] = probe_ofs.x;
c.probe_world_offset[1] = probe_ofs.y;
c.probe_world_offset[2] = probe_ofs.z;
@@ -682,18 +682,18 @@ void RenderForwardClustered::_setup_environment(RID p_environment, RID p_render_
scene_state.ubo.use_reflection_cubemap = false;
scene_state.ubo.ssao_enabled = false;
- } else if (is_environment(p_environment)) {
- RS::EnvironmentBG env_bg = environment_get_background(p_environment);
- RS::EnvironmentAmbientSource ambient_src = environment_get_ambient_source(p_environment);
+ } else if (is_environment(p_render_data->environment)) {
+ RS::EnvironmentBG env_bg = environment_get_background(p_render_data->environment);
+ RS::EnvironmentAmbientSource ambient_src = environment_get_ambient_source(p_render_data->environment);
- float bg_energy = environment_get_bg_energy(p_environment);
+ float bg_energy = environment_get_bg_energy(p_render_data->environment);
scene_state.ubo.ambient_light_color_energy[3] = bg_energy;
- scene_state.ubo.ambient_color_sky_mix = environment_get_ambient_sky_contribution(p_environment);
+ scene_state.ubo.ambient_color_sky_mix = environment_get_ambient_sky_contribution(p_render_data->environment);
//ambient
if (ambient_src == RS::ENV_AMBIENT_SOURCE_BG && (env_bg == RS::ENV_BG_CLEAR_COLOR || env_bg == RS::ENV_BG_COLOR)) {
- Color color = env_bg == RS::ENV_BG_CLEAR_COLOR ? p_default_bg_color : environment_get_bg_color(p_environment);
+ Color color = env_bg == RS::ENV_BG_CLEAR_COLOR ? p_default_bg_color : environment_get_bg_color(p_render_data->environment);
color = color.to_linear();
scene_state.ubo.ambient_light_color_energy[0] = color.r * bg_energy;
@@ -702,15 +702,15 @@ void RenderForwardClustered::_setup_environment(RID p_environment, RID p_render_
scene_state.ubo.use_ambient_light = true;
scene_state.ubo.use_ambient_cubemap = false;
} else {
- float energy = environment_get_ambient_light_energy(p_environment);
- Color color = environment_get_ambient_light_color(p_environment);
+ float energy = environment_get_ambient_light_energy(p_render_data->environment);
+ Color color = environment_get_ambient_light_color(p_render_data->environment);
color = color.to_linear();
scene_state.ubo.ambient_light_color_energy[0] = color.r * energy;
scene_state.ubo.ambient_light_color_energy[1] = color.g * energy;
scene_state.ubo.ambient_light_color_energy[2] = color.b * energy;
- Basis sky_transform = environment_get_sky_orientation(p_environment);
- sky_transform = sky_transform.inverse() * p_cam_transform.basis;
+ Basis sky_transform = environment_get_sky_orientation(p_render_data->environment);
+ sky_transform = sky_transform.inverse() * p_render_data->cam_transform.basis;
RendererStorageRD::store_transform_3x3(sky_transform, scene_state.ubo.radiance_inverse_xform);
scene_state.ubo.use_ambient_cubemap = (ambient_src == RS::ENV_AMBIENT_SOURCE_BG && env_bg == RS::ENV_BG_SKY) || ambient_src == RS::ENV_AMBIENT_SOURCE_SKY;
@@ -718,43 +718,43 @@ void RenderForwardClustered::_setup_environment(RID p_environment, RID p_render_
}
//specular
- RS::EnvironmentReflectionSource ref_src = environment_get_reflection_source(p_environment);
+ RS::EnvironmentReflectionSource ref_src = environment_get_reflection_source(p_render_data->environment);
if ((ref_src == RS::ENV_REFLECTION_SOURCE_BG && env_bg == RS::ENV_BG_SKY) || ref_src == RS::ENV_REFLECTION_SOURCE_SKY) {
scene_state.ubo.use_reflection_cubemap = true;
} else {
scene_state.ubo.use_reflection_cubemap = false;
}
- scene_state.ubo.ssao_enabled = p_opaque_render_buffers && environment_is_ssao_enabled(p_environment);
- scene_state.ubo.ssao_ao_affect = environment_get_ssao_ao_affect(p_environment);
- scene_state.ubo.ssao_light_affect = environment_get_ssao_light_affect(p_environment);
+ scene_state.ubo.ssao_enabled = p_opaque_render_buffers && environment_is_ssao_enabled(p_render_data->environment);
+ scene_state.ubo.ssao_ao_affect = environment_get_ssao_ao_affect(p_render_data->environment);
+ scene_state.ubo.ssao_light_affect = environment_get_ssao_light_affect(p_render_data->environment);
- Color ao_color = environment_get_ao_color(p_environment).to_linear();
+ Color ao_color = environment_get_ao_color(p_render_data->environment).to_linear();
scene_state.ubo.ao_color[0] = ao_color.r;
scene_state.ubo.ao_color[1] = ao_color.g;
scene_state.ubo.ao_color[2] = ao_color.b;
scene_state.ubo.ao_color[3] = ao_color.a;
- scene_state.ubo.fog_enabled = environment_is_fog_enabled(p_environment);
- scene_state.ubo.fog_density = environment_get_fog_density(p_environment);
- scene_state.ubo.fog_height = environment_get_fog_height(p_environment);
- scene_state.ubo.fog_height_density = environment_get_fog_height_density(p_environment);
+ scene_state.ubo.fog_enabled = environment_is_fog_enabled(p_render_data->environment);
+ scene_state.ubo.fog_density = environment_get_fog_density(p_render_data->environment);
+ scene_state.ubo.fog_height = environment_get_fog_height(p_render_data->environment);
+ scene_state.ubo.fog_height_density = environment_get_fog_height_density(p_render_data->environment);
if (scene_state.ubo.fog_height_density >= 0.0001) {
scene_state.ubo.fog_height_density = 1.0 / scene_state.ubo.fog_height_density;
}
- scene_state.ubo.fog_aerial_perspective = environment_get_fog_aerial_perspective(p_environment);
+ scene_state.ubo.fog_aerial_perspective = environment_get_fog_aerial_perspective(p_render_data->environment);
- Color fog_color = environment_get_fog_light_color(p_environment).to_linear();
- float fog_energy = environment_get_fog_light_energy(p_environment);
+ Color fog_color = environment_get_fog_light_color(p_render_data->environment).to_linear();
+ float fog_energy = environment_get_fog_light_energy(p_render_data->environment);
scene_state.ubo.fog_light_color[0] = fog_color.r * fog_energy;
scene_state.ubo.fog_light_color[1] = fog_color.g * fog_energy;
scene_state.ubo.fog_light_color[2] = fog_color.b * fog_energy;
- scene_state.ubo.fog_sun_scatter = environment_get_fog_sun_scatter(p_environment);
+ scene_state.ubo.fog_sun_scatter = environment_get_fog_sun_scatter(p_render_data->environment);
} else {
- if (p_reflection_probe.is_valid() && storage->reflection_probe_is_interior(reflection_probe_instance_get_probe(p_reflection_probe))) {
+ if (p_render_data->reflection_probe.is_valid() && storage->reflection_probe_is_interior(reflection_probe_instance_get_probe(p_render_data->reflection_probe))) {
scene_state.ubo.use_ambient_light = false;
} else {
scene_state.ubo.use_ambient_light = true;
@@ -867,7 +867,7 @@ void RenderForwardClustered::_fill_instance_data(RenderListType p_render_list, u
}
}
-void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, const PagedArray<GeometryInstance *> &p_instances, PassMode p_pass_mode, const CameraMatrix &p_cam_projection, const Transform &p_cam_transform, bool p_using_sdfgi, bool p_using_opaque_gi, const Plane &p_lod_plane, float p_lod_distance_multiplier, float p_screen_lod_threshold, bool p_append) {
+void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, const RenderDataRD *p_render_data, PassMode p_pass_mode, bool p_using_sdfgi, bool p_using_opaque_gi, bool p_append) {
if (p_render_list == RENDER_LIST_OPAQUE) {
scene_state.used_sss = false;
scene_state.used_screen_texture = false;
@@ -876,9 +876,9 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con
}
uint32_t lightmap_captures_used = 0;
- Plane near_plane(p_cam_transform.origin, -p_cam_transform.basis.get_axis(Vector3::AXIS_Z));
- near_plane.d += p_cam_projection.get_z_near();
- float z_max = p_cam_projection.get_z_far() - p_cam_projection.get_z_near();
+ Plane near_plane(p_render_data->cam_transform.origin, -p_render_data->cam_transform.basis.get_axis(Vector3::AXIS_Z));
+ near_plane.d += p_render_data->cam_projection.get_z_near();
+ float z_max = p_render_data->cam_projection.get_z_far() - p_render_data->cam_projection.get_z_near();
RenderList *rl = &render_list[p_render_list];
_update_dirty_geometry_instances();
@@ -892,8 +892,8 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con
//fill list
- for (int i = 0; i < (int)p_instances.size(); i++) {
- GeometryInstanceForwardClustered *inst = static_cast<GeometryInstanceForwardClustered *>(p_instances[i]);
+ for (int i = 0; i < (int)p_render_data->instances->size(); i++) {
+ GeometryInstanceForwardClustered *inst = static_cast<GeometryInstanceForwardClustered *>((*p_render_data->instances)[i]);
Vector3 support_min = inst->transformed_aabb.get_support(-near_plane.normal);
inst->depth = near_plane.distance_to(support_min);
@@ -987,13 +987,13 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con
// LOD
- if (p_screen_lod_threshold > 0.0 && storage->mesh_surface_has_lod(surf->surface)) {
+ if (p_render_data->screen_lod_threshold > 0.0 && storage->mesh_surface_has_lod(surf->surface)) {
//lod
- Vector3 lod_support_min = inst->transformed_aabb.get_support(-p_lod_plane.normal);
- Vector3 lod_support_max = inst->transformed_aabb.get_support(p_lod_plane.normal);
+ Vector3 lod_support_min = inst->transformed_aabb.get_support(-p_render_data->lod_camera_plane.normal);
+ Vector3 lod_support_max = inst->transformed_aabb.get_support(p_render_data->lod_camera_plane.normal);
- float distance_min = p_lod_plane.distance_to(lod_support_min);
- float distance_max = p_lod_plane.distance_to(lod_support_max);
+ float distance_min = p_render_data->lod_camera_plane.distance_to(lod_support_min);
+ float distance_max = p_render_data->lod_camera_plane.distance_to(lod_support_max);
float distance = 0.0;
@@ -1006,7 +1006,7 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con
distance = -distance_max;
}
- surf->sort.lod_index = storage->mesh_surface_get_lod(surf->surface, inst->lod_model_scale * inst->lod_bias, distance * p_lod_distance_multiplier, p_screen_lod_threshold);
+ surf->sort.lod_index = storage->mesh_surface_get_lod(surf->surface, inst->lod_model_scale * inst->lod_bias, distance * p_render_data->lod_distance_multiplier, p_render_data->screen_lod_threshold);
} else {
surf->sort.lod_index = 0;
}
@@ -1090,28 +1090,21 @@ void RenderForwardClustered::_setup_lightmaps(const PagedArray<RID> &p_lightmaps
}
}
-void RenderForwardClustered::_render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, const PagedArray<GeometryInstance *> &p_instances, const PagedArray<RID> &p_gi_probes, const PagedArray<RID> &p_lightmaps, RID p_environment, RID p_cluster_buffer, uint32_t p_cluster_size, uint32_t p_max_cluster_elements, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_bg_color, float p_screen_lod_threshold) {
+void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Color &p_default_bg_color) {
RenderBufferDataForwardClustered *render_buffer = nullptr;
- if (p_render_buffer.is_valid()) {
- render_buffer = (RenderBufferDataForwardClustered *)render_buffers_get_data(p_render_buffer);
+ if (p_render_data->render_buffers.is_valid()) {
+ render_buffer = (RenderBufferDataForwardClustered *)render_buffers_get_data(p_render_data->render_buffers);
}
- RendererSceneEnvironmentRD *env = get_environment(p_environment);
+ RendererSceneEnvironmentRD *env = get_environment(p_render_data->environment);
//first of all, make a new render pass
//fill up ubo
RENDER_TIMESTAMP("Setup 3D Scene");
- float lod_distance_multiplier = p_cam_projection.get_lod_multiplier();
- Plane lod_camera_plane(p_cam_transform.get_origin(), -p_cam_transform.basis.get_axis(Vector3::AXIS_Z));
-
- if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_DISABLE_LOD) {
- p_screen_lod_threshold = 0.0;
- }
-
//scene_state.ubo.subsurface_scatter_width = subsurface_scatter_size;
- Vector2 vp_he = p_cam_projection.get_viewport_half_extents();
+ Vector2 vp_he = p_render_data->cam_projection.get_viewport_half_extents();
scene_state.ubo.viewport_size[0] = vp_he.x;
scene_state.ubo.viewport_size[1] = vp_he.y;
scene_state.ubo.directional_light_count = 0;
@@ -1136,29 +1129,29 @@ void RenderForwardClustered::_render_scene(RID p_render_buffer, const Transform
opaque_framebuffer = render_buffer->color_fb;
- if (p_gi_probes.size() > 0) {
+ if (p_render_data->gi_probes->size() > 0) {
using_giprobe = true;
}
- if (!p_environment.is_valid() && using_giprobe) {
+ if (!p_render_data->environment.is_valid() && using_giprobe) {
depth_pass_mode = PASS_MODE_DEPTH_NORMAL_ROUGHNESS_GIPROBE;
- } else if (p_environment.is_valid() && (environment_is_ssr_enabled(p_environment) || environment_is_sdfgi_enabled(p_environment) || using_giprobe)) {
- if (environment_is_sdfgi_enabled(p_environment)) {
+ } else if (p_render_data->environment.is_valid() && (environment_is_ssr_enabled(p_render_data->environment) || environment_is_sdfgi_enabled(p_render_data->environment) || using_giprobe)) {
+ if (environment_is_sdfgi_enabled(p_render_data->environment)) {
depth_pass_mode = using_giprobe ? PASS_MODE_DEPTH_NORMAL_ROUGHNESS_GIPROBE : PASS_MODE_DEPTH_NORMAL_ROUGHNESS; // also giprobe
using_sdfgi = true;
} else {
depth_pass_mode = using_giprobe ? PASS_MODE_DEPTH_NORMAL_ROUGHNESS_GIPROBE : PASS_MODE_DEPTH_NORMAL_ROUGHNESS;
}
- if (environment_is_ssr_enabled(p_environment)) {
+ if (environment_is_ssr_enabled(p_render_data->environment)) {
render_buffer->ensure_specular();
using_separate_specular = true;
using_ssr = true;
opaque_specular_framebuffer = render_buffer->color_specular_fb;
}
- } else if (p_environment.is_valid() && (environment_is_ssao_enabled(p_environment) || get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_NORMAL_BUFFER)) {
+ } else if (p_render_data->environment.is_valid() && (environment_is_ssao_enabled(p_render_data->environment) || get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_NORMAL_BUFFER)) {
depth_pass_mode = PASS_MODE_DEPTH_NORMAL_ROUGHNESS;
}
@@ -1183,17 +1176,18 @@ void RenderForwardClustered::_render_scene(RID p_render_buffer, const Transform
}
alpha_framebuffer = opaque_framebuffer;
- } else if (p_reflection_probe.is_valid()) {
- uint32_t resolution = reflection_probe_instance_get_resolution(p_reflection_probe);
+ } else if (p_render_data->reflection_probe.is_valid()) {
+ uint32_t resolution = reflection_probe_instance_get_resolution(p_render_data->reflection_probe);
screen_size.x = resolution;
screen_size.y = resolution;
- opaque_framebuffer = reflection_probe_instance_get_framebuffer(p_reflection_probe, p_reflection_probe_pass);
- depth_framebuffer = reflection_probe_instance_get_depth_framebuffer(p_reflection_probe, p_reflection_probe_pass);
+ opaque_framebuffer = reflection_probe_instance_get_framebuffer(p_render_data->reflection_probe, p_render_data->reflection_probe_pass);
+ depth_framebuffer = reflection_probe_instance_get_depth_framebuffer(p_render_data->reflection_probe, p_render_data->reflection_probe_pass);
alpha_framebuffer = opaque_framebuffer;
- if (storage->reflection_probe_is_interior(reflection_probe_instance_get_probe(p_reflection_probe))) {
- p_environment = RID(); //no environment on interiors
+ if (storage->reflection_probe_is_interior(reflection_probe_instance_get_probe(p_render_data->reflection_probe))) {
+ p_render_data->environment = RID(); //no environment on interiors
+ env = nullptr;
}
reverse_cull = true; // for some reason our views are inverted
@@ -1203,13 +1197,13 @@ void RenderForwardClustered::_render_scene(RID p_render_buffer, const Transform
RD::get_singleton()->draw_command_begin_label("Render Setup");
- _setup_lightmaps(p_lightmaps, p_cam_transform);
- _setup_giprobes(p_gi_probes);
- _setup_environment(p_environment, p_render_buffer, p_cam_projection, p_cam_transform, p_reflection_probe, p_reflection_probe.is_valid(), screen_size, p_cluster_size, p_max_cluster_elements, p_shadow_atlas, !p_reflection_probe.is_valid(), p_default_bg_color, p_cam_projection.get_z_near(), p_cam_projection.get_z_far(), false);
+ _setup_lightmaps(*p_render_data->lightmaps, p_render_data->cam_transform);
+ _setup_giprobes(*p_render_data->gi_probes);
+ _setup_environment(p_render_data, p_render_data->reflection_probe.is_valid(), screen_size, !p_render_data->reflection_probe.is_valid(), p_default_bg_color, false);
_update_render_base_uniform_set(); //may have changed due to the above (light buffer enlarged, as an example)
- _fill_render_list(RENDER_LIST_OPAQUE, p_instances, PASS_MODE_COLOR, p_cam_projection, p_cam_transform, using_sdfgi, using_sdfgi || using_giprobe, lod_camera_plane, lod_distance_multiplier, p_screen_lod_threshold);
+ _fill_render_list(RENDER_LIST_OPAQUE, p_render_data, PASS_MODE_COLOR, using_sdfgi, using_sdfgi || using_giprobe);
render_list[RENDER_LIST_OPAQUE].sort_by_key();
render_list[RENDER_LIST_ALPHA].sort_by_depth();
_fill_instance_data(RENDER_LIST_OPAQUE);
@@ -1234,26 +1228,26 @@ void RenderForwardClustered::_render_scene(RID p_render_buffer, const Transform
if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_OVERDRAW) {
clear_color = Color(0, 0, 0, 1); //in overdraw mode, BG should always be black
- } else if (is_environment(p_environment)) {
- RS::EnvironmentBG bg_mode = environment_get_background(p_environment);
- float bg_energy = environment_get_bg_energy(p_environment);
+ } else if (is_environment(p_render_data->environment)) {
+ RS::EnvironmentBG bg_mode = environment_get_background(p_render_data->environment);
+ float bg_energy = environment_get_bg_energy(p_render_data->environment);
switch (bg_mode) {
case RS::ENV_BG_CLEAR_COLOR: {
clear_color = p_default_bg_color;
clear_color.r *= bg_energy;
clear_color.g *= bg_energy;
clear_color.b *= bg_energy;
- if (render_buffers_has_volumetric_fog(p_render_buffer) || environment_is_fog_enabled(p_environment)) {
+ if (render_buffers_has_volumetric_fog(p_render_data->render_buffers) || environment_is_fog_enabled(p_render_data->environment)) {
draw_sky_fog_only = true;
storage->material_set_param(sky.sky_scene_state.fog_material, "clear_color", Variant(clear_color.to_linear()));
}
} break;
case RS::ENV_BG_COLOR: {
- clear_color = environment_get_bg_color(p_environment);
+ clear_color = environment_get_bg_color(p_render_data->environment);
clear_color.r *= bg_energy;
clear_color.g *= bg_energy;
clear_color.b *= bg_energy;
- if (render_buffers_has_volumetric_fog(p_render_buffer) || environment_is_fog_enabled(p_environment)) {
+ if (render_buffers_has_volumetric_fog(p_render_data->render_buffers) || environment_is_fog_enabled(p_render_data->environment)) {
draw_sky_fog_only = true;
storage->material_set_param(sky.sky_scene_state.fog_material, "clear_color", Variant(clear_color.to_linear()));
}
@@ -1273,21 +1267,21 @@ void RenderForwardClustered::_render_scene(RID p_render_buffer, const Transform
}
}
// setup sky if used for ambient, reflections, or background
- if (draw_sky || draw_sky_fog_only || environment_get_reflection_source(p_environment) == RS::ENV_REFLECTION_SOURCE_SKY || environment_get_ambient_source(p_environment) == RS::ENV_AMBIENT_SOURCE_SKY) {
+ if (draw_sky || draw_sky_fog_only || environment_get_reflection_source(p_render_data->environment) == RS::ENV_REFLECTION_SOURCE_SKY || environment_get_ambient_source(p_render_data->environment) == RS::ENV_AMBIENT_SOURCE_SKY) {
RENDER_TIMESTAMP("Setup Sky");
RD::get_singleton()->draw_command_begin_label("Setup Sky");
- CameraMatrix projection = p_cam_projection;
- if (p_reflection_probe.is_valid()) {
+ CameraMatrix projection = p_render_data->cam_projection;
+ if (p_render_data->reflection_probe.is_valid()) {
CameraMatrix correction;
correction.set_depth_correction(true);
- projection = correction * p_cam_projection;
+ projection = correction * p_render_data->cam_projection;
}
- sky.setup(env, p_render_buffer, projection, p_cam_transform, screen_size, this);
+ sky.setup(env, p_render_data->render_buffers, projection, p_render_data->cam_transform, screen_size, this);
RID sky_rid = env->sky;
if (sky_rid.is_valid()) {
- sky.update(env, projection, p_cam_transform, time);
+ sky.update(env, projection, p_render_data->cam_transform, time);
radiance_texture = sky.sky_get_radiance_texture_rd(sky_rid);
} else {
// do not try to draw sky if invalid
@@ -1303,11 +1297,11 @@ void RenderForwardClustered::_render_scene(RID p_render_buffer, const Transform
bool debug_sdfgi_probes = get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_SDFGI_PROBES;
bool depth_pre_pass = depth_framebuffer.is_valid();
- bool using_ssao = depth_pre_pass && p_render_buffer.is_valid() && p_environment.is_valid() && environment_is_ssao_enabled(p_environment);
+ bool using_ssao = depth_pre_pass && p_render_data->render_buffers.is_valid() && p_render_data->environment.is_valid() && environment_is_ssao_enabled(p_render_data->environment);
bool continue_depth = false;
if (depth_pre_pass) { //depth pre pass
- bool needs_pre_resolve = _needs_post_prepass_render(using_sdfgi || using_giprobe);
+ bool needs_pre_resolve = _needs_post_prepass_render(p_render_data, using_sdfgi || using_giprobe);
if (needs_pre_resolve) {
RENDER_TIMESTAMP("GI + Render Depth Pre-Pass (parallel)");
} else {
@@ -1318,21 +1312,21 @@ void RenderForwardClustered::_render_scene(RID p_render_buffer, const Transform
RD::get_singleton()->draw_list_begin(depth_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_CONTINUE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_CONTINUE, depth_pass_clear);
RD::get_singleton()->draw_list_end();
//start compute processes here, so they run at the same time as depth pre-pass
- _post_prepass_render(using_sdfgi || using_giprobe);
+ _post_prepass_render(p_render_data, using_sdfgi || using_giprobe);
}
RD::get_singleton()->draw_command_begin_label("Render Depth Pre-Pass");
- RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_OPAQUE, RID(), RID(), RID(), RID(), RID(), PagedArray<RID>(), PagedArray<RID>());
+ RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_OPAQUE, nullptr, RID());
bool finish_depth = using_ssao || using_sdfgi || using_giprobe;
- RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, depth_pass_mode, render_buffer == nullptr, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), lod_camera_plane, lod_distance_multiplier, p_screen_lod_threshold);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, depth_pass_mode, render_buffer == nullptr, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->lod_camera_plane, p_render_data->lod_distance_multiplier, p_render_data->screen_lod_threshold);
_render_list_with_threads(&render_list_params, depth_framebuffer, needs_pre_resolve ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, needs_pre_resolve ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_CLEAR, finish_depth ? RD::FINAL_ACTION_READ : RD::FINAL_ACTION_CONTINUE, needs_pre_resolve ? Vector<Color>() : depth_pass_clear);
RD::get_singleton()->draw_command_end_label();
if (needs_pre_resolve) {
- _pre_resolve_render(using_sdfgi || using_giprobe);
+ _pre_resolve_render(p_render_data, using_sdfgi || using_giprobe);
}
if (render_buffer && render_buffer->msaa != RS::VIEWPORT_MSAA_DISABLED) {
@@ -1353,17 +1347,17 @@ void RenderForwardClustered::_render_scene(RID p_render_buffer, const Transform
continue_depth = !finish_depth;
}
- _pre_opaque_render(using_ssao, using_sdfgi || using_giprobe, render_buffer ? render_buffer->normal_roughness_buffer : RID(), render_buffer ? render_buffer->giprobe_buffer : RID());
+ _pre_opaque_render(p_render_data, using_ssao, using_sdfgi || using_giprobe, render_buffer ? render_buffer->normal_roughness_buffer : RID(), render_buffer ? render_buffer->giprobe_buffer : RID());
RD::get_singleton()->draw_command_begin_label("Render Opaque Pass");
- scene_state.ubo.directional_light_count = _get_render_state_directional_light_count();
+ scene_state.ubo.directional_light_count = p_render_data->directional_light_count;
- _setup_environment(p_environment, p_render_buffer, p_cam_projection, p_cam_transform, p_reflection_probe, p_reflection_probe.is_valid(), screen_size, p_cluster_size, p_max_cluster_elements, p_shadow_atlas, !p_reflection_probe.is_valid(), p_default_bg_color, p_cam_projection.get_z_near(), p_cam_projection.get_z_far(), p_render_buffer.is_valid());
+ _setup_environment(p_render_data, p_render_data->reflection_probe.is_valid(), screen_size, !p_render_data->reflection_probe.is_valid(), p_default_bg_color, p_render_data->render_buffers.is_valid());
RENDER_TIMESTAMP("Render Opaque Pass");
- RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_OPAQUE, p_render_buffer, radiance_texture, p_shadow_atlas, p_reflection_atlas, p_cluster_buffer, p_gi_probes, p_lightmaps, true);
+ RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_OPAQUE, p_render_data, radiance_texture, true);
bool can_continue_color = !scene_state.used_screen_texture && !using_ssr && !using_sss;
bool can_continue_depth = !scene_state.used_depth_texture && !using_ssr && !using_sss;
@@ -1384,7 +1378,7 @@ void RenderForwardClustered::_render_scene(RID p_render_buffer, const Transform
}
RID framebuffer = using_separate_specular ? opaque_specular_framebuffer : opaque_framebuffer;
- RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, using_separate_specular ? PASS_MODE_COLOR_SPECULAR : PASS_MODE_COLOR, render_buffer == nullptr, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), lod_camera_plane, lod_distance_multiplier, p_screen_lod_threshold);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, using_separate_specular ? PASS_MODE_COLOR_SPECULAR : PASS_MODE_COLOR, render_buffer == nullptr, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->lod_camera_plane, p_render_data->lod_distance_multiplier, p_render_data->screen_lod_threshold);
_render_list_with_threads(&render_list_params, framebuffer, keep_color ? RD::INITIAL_ACTION_KEEP : RD::INITIAL_ACTION_CLEAR, will_continue_color ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, depth_pre_pass ? (continue_depth ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_KEEP) : RD::INITIAL_ACTION_CLEAR, will_continue_depth ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, c, 1.0, 0);
if (will_continue_color && using_separate_specular) {
// close the specular framebuffer, as it's no longer used
@@ -1402,11 +1396,11 @@ void RenderForwardClustered::_render_scene(RID p_render_buffer, const Transform
CameraMatrix dc;
dc.set_depth_correction(true);
- CameraMatrix cm = (dc * p_cam_projection) * CameraMatrix(p_cam_transform.affine_inverse());
+ CameraMatrix cm = (dc * p_render_data->cam_projection) * CameraMatrix(p_render_data->cam_transform.affine_inverse());
RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(opaque_framebuffer, RD::INITIAL_ACTION_CONTINUE, will_continue_color ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CONTINUE, will_continue_depth ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ);
RD::get_singleton()->draw_command_begin_label("Debug GIProbes");
- for (int i = 0; i < (int)p_gi_probes.size(); i++) {
- gi.debug_giprobe(p_gi_probes[i], draw_list, opaque_framebuffer, cm, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_GI_PROBE_LIGHTING, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_GI_PROBE_EMISSION, 1.0);
+ for (int i = 0; i < (int)p_render_data->gi_probes->size(); i++) {
+ gi.debug_giprobe((*p_render_data->gi_probes)[i], draw_list, opaque_framebuffer, cm, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_GI_PROBE_LIGHTING, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_GI_PROBE_EMISSION, 1.0);
}
RD::get_singleton()->draw_command_end_label();
RD::get_singleton()->draw_list_end();
@@ -1419,10 +1413,10 @@ void RenderForwardClustered::_render_scene(RID p_render_buffer, const Transform
CameraMatrix dc;
dc.set_depth_correction(true);
- CameraMatrix cm = (dc * p_cam_projection) * CameraMatrix(p_cam_transform.affine_inverse());
+ CameraMatrix cm = (dc * p_render_data->cam_projection) * CameraMatrix(p_render_data->cam_transform.affine_inverse());
RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(opaque_framebuffer, RD::INITIAL_ACTION_CONTINUE, will_continue_color ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CONTINUE, will_continue_depth ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ);
RD::get_singleton()->draw_command_begin_label("Debug SDFGI");
- _debug_sdfgi_probes(p_render_buffer, draw_list, opaque_framebuffer, cm);
+ _debug_sdfgi_probes(p_render_data->render_buffers, draw_list, opaque_framebuffer, cm);
RD::get_singleton()->draw_command_end_label();
RD::get_singleton()->draw_list_end();
}
@@ -1430,14 +1424,14 @@ void RenderForwardClustered::_render_scene(RID p_render_buffer, const Transform
if (draw_sky || draw_sky_fog_only) {
RENDER_TIMESTAMP("Render Sky");
- CameraMatrix projection = p_cam_projection;
- if (p_reflection_probe.is_valid()) {
+ CameraMatrix projection = p_render_data->cam_projection;
+ if (p_render_data->reflection_probe.is_valid()) {
CameraMatrix correction;
correction.set_depth_correction(true);
- projection = correction * p_cam_projection;
+ projection = correction * p_render_data->cam_projection;
}
RD::get_singleton()->draw_command_begin_label("Draw Sky");
- sky.draw(env, can_continue_color, can_continue_depth, opaque_framebuffer, projection, p_cam_transform, time);
+ sky.draw(env, can_continue_color, can_continue_depth, opaque_framebuffer, projection, p_render_data->cam_transform, time);
RD::get_singleton()->draw_command_end_label();
}
@@ -1456,14 +1450,14 @@ void RenderForwardClustered::_render_scene(RID p_render_buffer, const Transform
if (using_sss) {
RENDER_TIMESTAMP("Sub Surface Scattering");
RD::get_singleton()->draw_command_begin_label("Process Sub Surface Scattering");
- _process_sss(p_render_buffer, p_cam_projection);
+ _process_sss(p_render_data->render_buffers, p_render_data->cam_projection);
RD::get_singleton()->draw_command_end_label();
}
if (using_ssr) {
RENDER_TIMESTAMP("Screen Space Reflection");
RD::get_singleton()->draw_command_begin_label("Process Screen Space Reflections");
- _process_ssr(p_render_buffer, render_buffer->color_fb, render_buffer->normal_roughness_buffer, render_buffer->specular, render_buffer->specular, Color(0, 0, 0, 1), p_environment, p_cam_projection, render_buffer->msaa == RS::VIEWPORT_MSAA_DISABLED);
+ _process_ssr(p_render_data->render_buffers, render_buffer->color_fb, render_buffer->normal_roughness_buffer, render_buffer->specular, render_buffer->specular, Color(0, 0, 0, 1), p_render_data->environment, p_render_data->cam_projection, render_buffer->msaa == RS::VIEWPORT_MSAA_DISABLED);
RD::get_singleton()->draw_command_end_label();
} else {
//just mix specular back
@@ -1476,12 +1470,12 @@ void RenderForwardClustered::_render_scene(RID p_render_buffer, const Transform
RD::get_singleton()->draw_command_begin_label("Render Transparent Pass");
- rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_ALPHA, p_render_buffer, radiance_texture, p_shadow_atlas, p_reflection_atlas, p_cluster_buffer, p_gi_probes, p_lightmaps, true);
+ rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_ALPHA, p_render_data, radiance_texture, true);
- _setup_environment(p_environment, p_render_buffer, p_cam_projection, p_cam_transform, p_reflection_probe, p_reflection_probe.is_valid(), screen_size, p_cluster_size, p_max_cluster_elements, p_shadow_atlas, !p_reflection_probe.is_valid(), p_default_bg_color, p_cam_projection.get_z_near(), p_cam_projection.get_z_far(), false);
+ _setup_environment(p_render_data, p_render_data->reflection_probe.is_valid(), screen_size, !p_render_data->reflection_probe.is_valid(), p_default_bg_color, false);
{
- RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), false, PASS_MODE_COLOR, render_buffer == nullptr, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), lod_camera_plane, lod_distance_multiplier, p_screen_lod_threshold);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), false, PASS_MODE_COLOR, render_buffer == nullptr, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->lod_camera_plane, p_render_data->lod_distance_multiplier, p_render_data->screen_lod_threshold);
_render_list_with_threads(&render_list_params, alpha_framebuffer, can_continue_color ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, can_continue_depth ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ);
}
@@ -1509,18 +1503,31 @@ void RenderForwardClustered::_render_shadow_append(RID p_framebuffer, const Page
SceneState::ShadowPass shadow_pass;
+ RenderDataRD render_data;
+ render_data.cam_projection = p_projection;
+ render_data.cam_transform = p_transform;
+ render_data.z_far = p_zfar;
+ render_data.z_near = 0.0;
+ render_data.cluster_size = 1;
+ render_data.cluster_max_elements = 32;
+ render_data.instances = &p_instances;
+ render_data.lod_camera_plane = p_camera_plane;
+ render_data.lod_distance_multiplier = p_lod_distance_multiplier;
+
scene_state.ubo.dual_paraboloid_side = p_use_dp_flip ? -1 : 1;
- _setup_environment(RID(), RID(), p_projection, p_transform, RID(), true, Vector2(1, 1), 1, 32, RID(), !p_flip_y, Color(), 0, p_zfar, false, p_use_pancake, shadow_pass_index);
+ _setup_environment(&render_data, true, Vector2(1, 1), !p_flip_y, Color(), false, p_use_pancake, shadow_pass_index);
if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_DISABLE_LOD) {
- p_screen_lod_threshold = 0.0;
+ render_data.screen_lod_threshold = 0.0;
+ } else {
+ render_data.screen_lod_threshold = p_screen_lod_threshold;
}
PassMode pass_mode = p_use_dp ? PASS_MODE_SHADOW_DP : PASS_MODE_SHADOW;
uint32_t render_list_from = render_list[RENDER_LIST_SECONDARY].elements.size();
- _fill_render_list(RENDER_LIST_SECONDARY, p_instances, pass_mode, p_projection, p_transform, false, false, p_camera_plane, p_lod_distance_multiplier, p_screen_lod_threshold, true);
+ _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode, false, false, true);
uint32_t render_list_size = render_list[RENDER_LIST_SECONDARY].elements.size() - render_list_from;
render_list[RENDER_LIST_SECONDARY].sort_by_key_range(render_list_from, render_list_size);
_fill_instance_data(RENDER_LIST_SECONDARY, render_list_from, render_list_size, false);
@@ -1539,8 +1546,8 @@ void RenderForwardClustered::_render_shadow_append(RID p_framebuffer, const Page
shadow_pass.rp_uniform_set = RID(); //will be filled later when instance buffer is complete
shadow_pass.camera_plane = p_camera_plane;
- shadow_pass.screen_lod_threshold = p_screen_lod_threshold;
- shadow_pass.lod_distance_multiplier = p_lod_distance_multiplier;
+ shadow_pass.screen_lod_threshold = render_data.screen_lod_threshold;
+ shadow_pass.lod_distance_multiplier = render_data.lod_distance_multiplier;
shadow_pass.framebuffer = p_framebuffer;
shadow_pass.initial_depth_action = p_begin ? (p_clear_region ? RD::INITIAL_ACTION_CLEAR_REGION : RD::INITIAL_ACTION_CLEAR) : (p_clear_region ? RD::INITIAL_ACTION_CLEAR_REGION_CONTINUE : RD::INITIAL_ACTION_CONTINUE);
@@ -1558,7 +1565,7 @@ void RenderForwardClustered::_render_shadow_process() {
for (uint32_t i = 0; i < scene_state.shadow_passes.size(); i++) {
//render passes need to be configured after instance buffer is done, since they need the latest version
SceneState::ShadowPass &shadow_pass = scene_state.shadow_passes[i];
- shadow_pass.rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, RID(), RID(), RID(), RID(), RID(), PagedArray<RID>(), PagedArray<RID>(), false, i);
+ shadow_pass.rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, nullptr, RID(), false, i);
}
RD::get_singleton()->draw_command_end_label();
@@ -1583,18 +1590,27 @@ void RenderForwardClustered::_render_particle_collider_heightfield(RID p_fb, con
RD::get_singleton()->draw_command_begin_label("Render Collider Heightfield");
+ RenderDataRD render_data;
+ render_data.cam_projection = p_cam_projection;
+ render_data.cam_transform = p_cam_transform;
+ render_data.z_near = 0.0;
+ render_data.z_far = p_cam_projection.get_z_far();
+ render_data.cluster_size = 1;
+ render_data.cluster_max_elements = 32;
+ render_data.instances = &p_instances;
+
_update_render_base_uniform_set();
scene_state.ubo.dual_paraboloid_side = 0;
- _setup_environment(RID(), RID(), p_cam_projection, p_cam_transform, RID(), true, Vector2(1, 1), 1, 32, RID(), true, Color(), 0, p_cam_projection.get_z_far(), false, false);
+ _setup_environment(&render_data, true, Vector2(1, 1), true, Color(), false, false);
PassMode pass_mode = PASS_MODE_SHADOW;
- _fill_render_list(RENDER_LIST_SECONDARY, p_instances, pass_mode, p_cam_projection, p_cam_transform);
+ _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode);
render_list[RENDER_LIST_SECONDARY].sort_by_key();
_fill_instance_data(RENDER_LIST_SECONDARY);
- RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, RID(), RID(), RID(), RID(), RID(), PagedArray<RID>(), PagedArray<RID>());
+ RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, nullptr, RID());
RENDER_TIMESTAMP("Render Collider Heightfield");
@@ -1611,19 +1627,26 @@ void RenderForwardClustered::_render_material(const Transform &p_cam_transform,
RD::get_singleton()->draw_command_begin_label("Render Material");
+ RenderDataRD render_data;
+ render_data.cam_projection = p_cam_projection;
+ render_data.cam_transform = p_cam_transform;
+ render_data.cluster_size = 1;
+ render_data.cluster_max_elements = 32;
+ render_data.instances = &p_instances;
+
_update_render_base_uniform_set();
scene_state.ubo.dual_paraboloid_side = 0;
scene_state.ubo.material_uv2_mode = false;
- _setup_environment(RID(), RID(), p_cam_projection, p_cam_transform, RID(), true, Vector2(1, 1), 1, 32, RID(), false, Color(), 0, 0);
+ _setup_environment(&render_data, true, Vector2(1, 1), false, Color());
PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL;
- _fill_render_list(RENDER_LIST_SECONDARY, p_instances, pass_mode, p_cam_projection, p_cam_transform);
+ _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode);
render_list[RENDER_LIST_SECONDARY].sort_by_key();
_fill_instance_data(RENDER_LIST_SECONDARY);
- RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, RID(), RID(), RID(), RID(), RID(), PagedArray<RID>(), PagedArray<RID>());
+ RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, nullptr, RID());
RENDER_TIMESTAMP("Render Material");
@@ -1649,19 +1672,24 @@ void RenderForwardClustered::_render_uv2(const PagedArray<GeometryInstance *> &p
RD::get_singleton()->draw_command_begin_label("Render UV2");
+ RenderDataRD render_data;
+ render_data.cluster_size = 1;
+ render_data.cluster_max_elements = 32;
+ render_data.instances = &p_instances;
+
_update_render_base_uniform_set();
scene_state.ubo.dual_paraboloid_side = 0;
scene_state.ubo.material_uv2_mode = true;
- _setup_environment(RID(), RID(), CameraMatrix(), Transform(), RID(), true, Vector2(1, 1), 1, 32, RID(), false, Color(), 0, 0);
+ _setup_environment(&render_data, true, Vector2(1, 1), false, Color());
PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL;
- _fill_render_list(RENDER_LIST_SECONDARY, p_instances, pass_mode, CameraMatrix(), Transform());
+ _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode);
render_list[RENDER_LIST_SECONDARY].sort_by_key();
_fill_instance_data(RENDER_LIST_SECONDARY);
- RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, RID(), RID(), RID(), RID(), RID(), PagedArray<RID>(), PagedArray<RID>());
+ RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, nullptr, RID());
RENDER_TIMESTAMP("Render Material");
@@ -1712,13 +1740,18 @@ void RenderForwardClustered::_render_sdfgi(RID p_render_buffers, const Vector3i
RD::get_singleton()->draw_command_begin_label("Render SDFGI Voxel");
+ RenderDataRD render_data;
+ render_data.cluster_size = 1;
+ render_data.cluster_max_elements = 32;
+ render_data.instances = &p_instances;
+
_update_render_base_uniform_set();
RenderBufferDataForwardClustered *render_buffer = (RenderBufferDataForwardClustered *)render_buffers_get_data(p_render_buffers);
ERR_FAIL_COND(!render_buffer);
PassMode pass_mode = PASS_MODE_SDF;
- _fill_render_list(RENDER_LIST_SECONDARY, p_instances, pass_mode, CameraMatrix(), Transform());
+ _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode);
render_list[RENDER_LIST_SECONDARY].sort_by_key();
_fill_instance_data(RENDER_LIST_SECONDARY);
@@ -1750,28 +1783,26 @@ void RenderForwardClustered::_render_sdfgi(RID p_render_buffers, const Vector3i
fb_size.x = p_size[right_axis];
fb_size.y = p_size[up_axis];
- Transform cam_xform;
- cam_xform.origin = center + axis * half_extents;
- cam_xform.basis.set_axis(0, right);
- cam_xform.basis.set_axis(1, up);
- cam_xform.basis.set_axis(2, axis);
+ render_data.cam_transform.origin = center + axis * half_extents;
+ render_data.cam_transform.basis.set_axis(0, right);
+ render_data.cam_transform.basis.set_axis(1, up);
+ render_data.cam_transform.basis.set_axis(2, axis);
- //print_line("pass: " + itos(i) + " xform " + cam_xform);
+ //print_line("pass: " + itos(i) + " xform " + render_data.cam_transform);
float h_size = half_extents[right_axis];
float v_size = half_extents[up_axis];
float d_size = half_extents[i] * 2.0;
- CameraMatrix camera_proj;
- camera_proj.set_orthogonal(-h_size, h_size, -v_size, v_size, 0, d_size);
+ render_data.cam_projection.set_orthogonal(-h_size, h_size, -v_size, v_size, 0, d_size);
//print_line("pass: " + itos(i) + " cam hsize: " + rtos(h_size) + " vsize: " + rtos(v_size) + " dsize " + rtos(d_size));
Transform to_bounds;
to_bounds.origin = p_bounds.position;
to_bounds.basis.scale(p_bounds.size);
- RendererStorageRD::store_transform(to_bounds.affine_inverse() * cam_xform, scene_state.ubo.sdf_to_bounds);
+ RendererStorageRD::store_transform(to_bounds.affine_inverse() * render_data.cam_transform, scene_state.ubo.sdf_to_bounds);
- _setup_environment(RID(), RID(), camera_proj, cam_xform, RID(), true, Vector2(1, 1), 1, 32, RID(), false, Color(), 0, 0);
+ _setup_environment(&render_data, true, Vector2(1, 1), false, Color());
RID rp_uniform_set = _setup_sdfgi_render_pass_uniform_set(p_albedo_texture, p_emission_texture, p_emission_aniso_texture, p_geom_facing_texture);
@@ -1921,13 +1952,13 @@ void RenderForwardClustered::_update_render_base_uniform_set() {
}
}
-RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_render_list, RID p_render_buffers, RID p_radiance_texture, RID p_shadow_atlas, RID p_reflection_atlas, RID p_cluster_buffer, const PagedArray<RID> &p_gi_probes, const PagedArray<RID> &p_lightmaps, bool p_use_directional_shadow_atlas, int p_index) {
+RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_render_list, const RenderDataRD *p_render_data, RID p_radiance_texture, bool p_use_directional_shadow_atlas, int p_index) {
//there should always be enough uniform buffers for render passes, otherwise bugs
ERR_FAIL_INDEX_V(p_index, (int)scene_state.uniform_buffers.size(), RID());
RenderBufferDataForwardClustered *rb = nullptr;
- if (p_render_buffers.is_valid()) {
- rb = (RenderBufferDataForwardClustered *)render_buffers_get_data(p_render_buffers);
+ if (p_render_data && p_render_data->render_buffers.is_valid()) {
+ rb = (RenderBufferDataForwardClustered *)render_buffers_get_data(p_render_data->render_buffers);
}
//default render buffer and scene state uniform set
@@ -1967,7 +1998,7 @@ RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_rend
}
{
- RID ref_texture = p_reflection_atlas.is_valid() ? reflection_atlas_get_texture(p_reflection_atlas) : RID();
+ RID ref_texture = (p_render_data && p_render_data->reflection_atlas.is_valid()) ? reflection_atlas_get_texture(p_render_data->reflection_atlas) : RID();
RD::Uniform u;
u.binding = 3;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
@@ -1984,8 +2015,8 @@ RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_rend
u.binding = 4;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
RID texture;
- if (p_shadow_atlas.is_valid()) {
- texture = shadow_atlas_get_texture(p_shadow_atlas);
+ if (p_render_data && p_render_data->shadow_atlas.is_valid()) {
+ texture = shadow_atlas_get_texture(p_render_data->shadow_atlas);
}
if (!texture.is_valid()) {
texture = storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_WHITE);
@@ -2011,8 +2042,8 @@ RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_rend
u.ids.resize(scene_state.max_lightmaps);
RID default_tex = storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_2D_ARRAY_WHITE);
for (uint32_t i = 0; i < scene_state.max_lightmaps; i++) {
- if (i < p_lightmaps.size()) {
- RID base = lightmap_instance_get_lightmap(p_lightmaps[i]);
+ if (p_render_data && i < p_render_data->lightmaps->size()) {
+ RID base = lightmap_instance_get_lightmap((*p_render_data->lightmaps)[i]);
RID texture = storage->lightmap_get_texture(base);
RID rd_texture = storage->texture_get_rd_texture(texture);
u.ids.write[i] = rd_texture;
@@ -2030,8 +2061,8 @@ RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_rend
u.ids.resize(MAX_GI_PROBES);
RID default_tex = storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_3D_WHITE);
for (int i = 0; i < MAX_GI_PROBES; i++) {
- if (i < (int)p_gi_probes.size()) {
- RID tex = gi.gi_probe_instance_get_texture(p_gi_probes[i]);
+ if (p_render_data && i < (int)p_render_data->gi_probes->size()) {
+ RID tex = gi.gi_probe_instance_get_texture((*p_render_data->gi_probes)[i]);
if (!tex.is_valid()) {
tex = default_tex;
}
@@ -2048,7 +2079,7 @@ RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_rend
RD::Uniform u;
u.binding = 8;
u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
- RID cb = p_cluster_buffer.is_valid() ? p_cluster_buffer : scene_shader.default_vec4_xform_buffer;
+ RID cb = (p_render_data && p_render_data->cluster_buffer.is_valid()) ? p_render_data->cluster_buffer : scene_shader.default_vec4_xform_buffer;
u.ids.push_back(cb);
uniforms.push_back(u);
}
@@ -2065,7 +2096,7 @@ RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_rend
RD::Uniform u;
u.binding = 10;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
- RID bbt = rb ? render_buffers_get_back_buffer_texture(p_render_buffers) : RID();
+ RID bbt = rb ? render_buffers_get_back_buffer_texture(p_render_data->render_buffers) : RID();
RID texture = bbt.is_valid() ? bbt : storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_BLACK);
u.ids.push_back(texture);
uniforms.push_back(u);
@@ -2085,7 +2116,7 @@ RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_rend
RD::Uniform u;
u.binding = 12;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
- RID aot = rb ? render_buffers_get_ao_texture(p_render_buffers) : RID();
+ RID aot = rb ? render_buffers_get_ao_texture(p_render_data->render_buffers) : RID();
RID texture = aot.is_valid() ? aot : storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_BLACK);
u.ids.push_back(texture);
uniforms.push_back(u);
@@ -2095,7 +2126,7 @@ RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_rend
RD::Uniform u;
u.binding = 13;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
- RID ambient_buffer = p_render_buffers.is_valid() ? render_buffers_get_gi_ambient_texture(p_render_buffers) : RID();
+ RID ambient_buffer = rb ? render_buffers_get_gi_ambient_texture(p_render_data->render_buffers) : RID();
RID texture = ambient_buffer.is_valid() ? ambient_buffer : storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_BLACK);
u.ids.push_back(texture);
uniforms.push_back(u);
@@ -2105,7 +2136,7 @@ RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_rend
RD::Uniform u;
u.binding = 14;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
- RID reflection_buffer = p_render_buffers.is_valid() ? render_buffers_get_gi_reflection_texture(p_render_buffers) : RID();
+ RID reflection_buffer = rb ? render_buffers_get_gi_reflection_texture(p_render_data->render_buffers) : RID();
RID texture = reflection_buffer.is_valid() ? reflection_buffer : storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_BLACK);
u.ids.push_back(texture);
uniforms.push_back(u);
@@ -2115,8 +2146,8 @@ RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_rend
u.binding = 15;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
RID t;
- if (rb && render_buffers_is_sdfgi_enabled(p_render_buffers)) {
- t = render_buffers_get_sdfgi_irradiance_probes(p_render_buffers);
+ if (rb && render_buffers_is_sdfgi_enabled(p_render_data->render_buffers)) {
+ t = render_buffers_get_sdfgi_irradiance_probes(p_render_data->render_buffers);
} else {
t = storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_2D_ARRAY_WHITE);
}
@@ -2127,8 +2158,8 @@ RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_rend
RD::Uniform u;
u.binding = 16;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
- if (rb && render_buffers_is_sdfgi_enabled(p_render_buffers)) {
- u.ids.push_back(render_buffers_get_sdfgi_occlusion_texture(p_render_buffers));
+ if (rb && render_buffers_is_sdfgi_enabled(p_render_data->render_buffers)) {
+ u.ids.push_back(render_buffers_get_sdfgi_occlusion_texture(p_render_data->render_buffers));
} else {
u.ids.push_back(storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_3D_WHITE));
}
@@ -2138,7 +2169,7 @@ RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_rend
RD::Uniform u;
u.binding = 17;
u.uniform_type = RD::UNIFORM_TYPE_UNIFORM_BUFFER;
- u.ids.push_back(rb ? render_buffers_get_gi_probe_buffer(p_render_buffers) : render_buffers_get_default_gi_probe_buffer());
+ u.ids.push_back(rb ? render_buffers_get_gi_probe_buffer(p_render_data->render_buffers) : render_buffers_get_default_gi_probe_buffer());
uniforms.push_back(u);
}
{
@@ -2146,8 +2177,8 @@ RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_rend
u.binding = 18;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
RID vfog = RID();
- if (rb && render_buffers_has_volumetric_fog(p_render_buffers)) {
- vfog = render_buffers_get_volumetric_fog_texture(p_render_buffers);
+ if (rb && render_buffers_has_volumetric_fog(p_render_data->render_buffers)) {
+ vfog = render_buffers_get_volumetric_fog_texture(p_render_data->render_buffers);
if (vfog.is_null()) {
vfog = storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_3D_WHITE);
}
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
index 4b998a9e76..bed3c3b219 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
@@ -122,7 +122,7 @@ class RenderForwardClustered : public RendererSceneRenderRD {
void _update_render_base_uniform_set();
RID _setup_sdfgi_render_pass_uniform_set(RID p_albedo_texture, RID p_emission_texture, RID p_emission_aniso_texture, RID p_geom_facing_texture);
- RID _setup_render_pass_uniform_set(RenderListType p_render_list, RID p_render_buffers, RID p_radiance_texture, RID p_shadow_atlas, RID p_reflection_atlas, RID p_cluster_buffer, const PagedArray<RID> &p_gi_probes, const PagedArray<RID> &p_lightmaps, bool p_use_directional_shadow_atlas = false, int p_index = 0);
+ RID _setup_render_pass_uniform_set(RenderListType p_render_list, const RenderDataRD *p_render_data, RID p_radiance_texture, bool p_use_directional_shadow_atlas = false, int p_index = 0);
enum PassMode {
PASS_MODE_COLOR,
@@ -349,7 +349,7 @@ class RenderForwardClustered : public RendererSceneRenderRD {
static RenderForwardClustered *singleton;
- void _setup_environment(RID p_environment, RID p_render_buffers, const CameraMatrix &p_cam_projection, const Transform &p_cam_transform, RID p_reflection_probe, bool p_no_fog, const Size2i &p_screen_size, uint32_t p_cluster_size, uint32_t p_max_cluster_elements, RID p_shadow_atlas, bool p_flip_y, const Color &p_default_bg_color, float p_znear, float p_zfar, bool p_opaque_render_buffers = false, bool p_pancake_shadows = false, int p_index = 0);
+ void _setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_opaque_render_buffers = false, bool p_pancake_shadows = false, int p_index = 0);
void _setup_giprobes(const PagedArray<RID> &p_giprobes);
void _setup_lightmaps(const PagedArray<RID> &p_lightmaps, const Transform &p_cam_transform);
@@ -373,7 +373,7 @@ class RenderForwardClustered : public RendererSceneRenderRD {
void _update_instance_data_buffer(RenderListType p_render_list);
void _fill_instance_data(RenderListType p_render_list, uint32_t p_offset = 0, int32_t p_max_elements = -1, bool p_update_buffer = true);
- void _fill_render_list(RenderListType p_render_list, const PagedArray<GeometryInstance *> &p_instances, PassMode p_pass_mode, const CameraMatrix &p_cam_projection, const Transform &p_cam_transform, bool p_using_sdfgi = false, bool p_using_opaque_gi = false, const Plane &p_lod_camera_plane = Plane(), float p_lod_distance_multiplier = 0.0, float p_screen_lod_threshold = 0.0, bool p_append = false);
+ void _fill_render_list(RenderListType p_render_list, const RenderDataRD *p_render_data, PassMode p_pass_mode, bool p_using_sdfgi = false, bool p_using_opaque_gi = false, bool p_append = false);
Map<Size2i, RID> sdfgi_framebuffer_size_cache;
@@ -566,7 +566,7 @@ class RenderForwardClustered : public RendererSceneRenderRD {
RenderList render_list[RENDER_LIST_MAX];
protected:
- virtual void _render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, const PagedArray<GeometryInstance *> &p_instances, const PagedArray<RID> &p_gi_probes, const PagedArray<RID> &p_lightmaps, RID p_environment, RID p_cluster_buffer, uint32_t p_cluster_size, uint32_t p_max_cluster_elements, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_bg_color, float p_lod_threshold);
+ virtual void _render_scene(RenderDataRD *p_render_data, const Color &p_default_bg_color);
virtual void _render_shadow_begin();
virtual void _render_shadow_append(RID p_framebuffer, const PagedArray<GeometryInstance *> &p_instances, const CameraMatrix &p_projection, const Transform &p_transform, float p_zfar, float p_bias, float p_normal_bias, bool p_use_dp, bool p_use_dp_flip, bool p_use_pancake, const Plane &p_camera_plane = Plane(), float p_lod_distance_multiplier = 0.0, float p_screen_lod_threshold = 0.0, const Rect2i &p_rect = Rect2i(), bool p_flip_y = false, bool p_clear_region = true, bool p_begin = true, bool p_end = true);
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
index 041476adf3..4e93fa5333 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
@@ -125,13 +125,13 @@ bool RenderForwardMobile::free(RID p_rid) {
/* Render functions */
-RID RenderForwardMobile::_setup_render_pass_uniform_set(RenderListType p_render_list, RID p_render_buffers, RID p_radiance_texture, RID p_shadow_atlas, RID p_reflection_atlas, const PagedArray<RID> &p_lightmaps, bool p_use_directional_shadow_atlas, int p_index) {
+RID RenderForwardMobile::_setup_render_pass_uniform_set(RenderListType p_render_list, const RenderDataRD *p_render_data, RID p_radiance_texture, bool p_use_directional_shadow_atlas, int p_index) {
//there should always be enough uniform buffers for render passes, otherwise bugs
ERR_FAIL_INDEX_V(p_index, (int)scene_state.uniform_buffers.size(), RID());
RenderBufferDataForwardMobile *rb = nullptr;
- if (p_render_buffers.is_valid()) {
- rb = (RenderBufferDataForwardMobile *)render_buffers_get_data(p_render_buffers);
+ if (p_render_data && p_render_data->render_buffers.is_valid()) {
+ rb = (RenderBufferDataForwardMobile *)render_buffers_get_data(p_render_data->render_buffers);
}
// default render buffer and scene state uniform set
@@ -162,7 +162,7 @@ RID RenderForwardMobile::_setup_render_pass_uniform_set(RenderListType p_render_
}
{
- RID ref_texture = p_reflection_atlas.is_valid() ? reflection_atlas_get_texture(p_reflection_atlas) : RID();
+ RID ref_texture = (p_render_data && p_render_data->reflection_atlas.is_valid()) ? reflection_atlas_get_texture(p_render_data->reflection_atlas) : RID();
RD::Uniform u;
u.binding = 3;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
@@ -179,8 +179,8 @@ RID RenderForwardMobile::_setup_render_pass_uniform_set(RenderListType p_render_
u.binding = 4;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
RID texture;
- if (p_shadow_atlas.is_valid()) {
- texture = shadow_atlas_get_texture(p_shadow_atlas);
+ if (p_render_data && p_render_data->shadow_atlas.is_valid()) {
+ texture = shadow_atlas_get_texture(p_render_data->shadow_atlas);
}
if (!texture.is_valid()) {
texture = storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_WHITE);
@@ -208,8 +208,8 @@ RID RenderForwardMobile::_setup_render_pass_uniform_set(RenderListType p_render_
u.ids.resize(scene_state.max_lightmaps);
RID default_tex = storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_2D_ARRAY_WHITE);
for (uint32_t i = 0; i < scene_state.max_lightmaps; i++) {
- if (i < p_lightmaps.size()) {
- RID base = lightmap_instance_get_lightmap(p_lightmaps[i]);
+ if (p_render_data && i < p_render_data->lightmaps->size()) {
+ RID base = lightmap_instance_get_lightmap((*p_render_data->lightmaps)[i]);
RID texture = storage->lightmap_get_texture(base);
RID rd_texture = storage->texture_get_rd_texture(texture);
u.ids.write[i] = rd_texture;
@@ -265,7 +265,7 @@ RID RenderForwardMobile::_setup_render_pass_uniform_set(RenderListType p_render_
RD::Uniform u;
u.binding = 10;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
- RID bbt = rb ? render_buffers_get_back_buffer_texture(p_render_buffers) : RID();
+ RID bbt = rb ? render_buffers_get_back_buffer_texture(p_render_data->render_buffers) : RID();
RID texture = bbt.is_valid() ? bbt : storage->texture_rd_get_default(RendererStorageRD::DEFAULT_RD_TEXTURE_BLACK);
u.ids.push_back(texture);
uniforms.push_back(u);
@@ -306,29 +306,16 @@ void RenderForwardMobile::_setup_lightmaps(const PagedArray<RID> &p_lightmaps, c
}
}
-void RenderForwardMobile::_render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, const PagedArray<GeometryInstance *> &p_instances, const PagedArray<RID> &p_gi_probes, const PagedArray<RID> &p_lightmaps, RID p_environment, RID p_cluster_buffer, uint32_t p_cluster_size, uint32_t p_cluster_max_elements, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_bg_color, float p_screen_lod_threshold) {
- // These are UNUSED here and will not have data parsed from RendererSceneRenderRD:
- // - p_gi_probes
- // - p_cluster_buffer
- // - p_cluster_size
- // - p_cluster_max_elements
-
+void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color &p_default_bg_color) {
RenderBufferDataForwardMobile *render_buffer = nullptr;
- if (p_render_buffer.is_valid()) {
- render_buffer = (RenderBufferDataForwardMobile *)render_buffers_get_data(p_render_buffer);
+ if (p_render_data->render_buffers.is_valid()) {
+ render_buffer = (RenderBufferDataForwardMobile *)render_buffers_get_data(p_render_data->render_buffers);
}
- RendererSceneEnvironmentRD *env = get_environment(p_environment);
+ RendererSceneEnvironmentRD *env = get_environment(p_render_data->environment);
RENDER_TIMESTAMP("Setup 3D Scene");
- float lod_distance_multiplier = p_cam_projection.get_lod_multiplier();
- Plane lod_camera_plane(p_cam_transform.get_origin(), -p_cam_transform.basis.get_axis(Vector3::AXIS_Z));
-
- if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_DISABLE_LOD) {
- p_screen_lod_threshold = 0.0;
- }
-
- Vector2 vp_he = p_cam_projection.get_viewport_half_extents();
+ Vector2 vp_he = p_render_data->cam_projection.get_viewport_half_extents();
scene_state.ubo.viewport_size[0] = vp_he.x;
scene_state.ubo.viewport_size[1] = vp_he.y;
scene_state.ubo.directional_light_count = 0;
@@ -349,16 +336,17 @@ void RenderForwardMobile::_render_scene(RID p_render_buffer, const Transform &p_
opaque_framebuffer = render_buffer->color_fb;
alpha_framebuffer = opaque_framebuffer;
- } else if (p_reflection_probe.is_valid()) {
- uint32_t resolution = reflection_probe_instance_get_resolution(p_reflection_probe);
+ } else if (p_render_data->reflection_probe.is_valid()) {
+ uint32_t resolution = reflection_probe_instance_get_resolution(p_render_data->reflection_probe);
screen_size.x = resolution;
screen_size.y = resolution;
- opaque_framebuffer = reflection_probe_instance_get_framebuffer(p_reflection_probe, p_reflection_probe_pass);
+ opaque_framebuffer = reflection_probe_instance_get_framebuffer(p_render_data->reflection_probe, p_render_data->reflection_probe_pass);
alpha_framebuffer = opaque_framebuffer;
- if (storage->reflection_probe_is_interior(reflection_probe_instance_get_probe(p_reflection_probe))) {
- p_environment = RID(); //no environment on interiors
+ if (storage->reflection_probe_is_interior(reflection_probe_instance_get_probe(p_render_data->reflection_probe))) {
+ p_render_data->environment = RID(); //no environment on interiors
+ env = nullptr;
}
reverse_cull = true;
@@ -368,12 +356,12 @@ void RenderForwardMobile::_render_scene(RID p_render_buffer, const Transform &p_
RD::get_singleton()->draw_command_begin_label("Render Setup");
- _setup_lightmaps(p_lightmaps, p_cam_transform);
- _setup_environment(p_environment, p_render_buffer, p_cam_projection, p_cam_transform, p_reflection_probe, p_reflection_probe.is_valid(), screen_size, p_shadow_atlas, !p_reflection_probe.is_valid(), p_default_bg_color, p_cam_projection.get_z_near(), p_cam_projection.get_z_far(), false);
+ _setup_lightmaps(*p_render_data->lightmaps, p_render_data->cam_transform);
+ _setup_environment(p_render_data, p_render_data->reflection_probe.is_valid(), screen_size, !p_render_data->reflection_probe.is_valid(), p_default_bg_color, false);
_update_render_base_uniform_set(); //may have changed due to the above (light buffer enlarged, as an example)
- _fill_render_list(RENDER_LIST_OPAQUE, p_instances, PASS_MODE_COLOR, p_cam_projection, p_cam_transform, lod_camera_plane, lod_distance_multiplier, p_screen_lod_threshold);
+ _fill_render_list(RENDER_LIST_OPAQUE, p_render_data, PASS_MODE_COLOR);
render_list[RENDER_LIST_OPAQUE].sort_by_key();
render_list[RENDER_LIST_ALPHA].sort_by_depth();
@@ -395,9 +383,9 @@ void RenderForwardMobile::_render_scene(RID p_render_buffer, const Transform &p_
if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_OVERDRAW) {
clear_color = Color(0, 0, 0, 1); //in overdraw mode, BG should always be black
- } else if (is_environment(p_environment)) {
- RS::EnvironmentBG bg_mode = environment_get_background(p_environment);
- float bg_energy = environment_get_bg_energy(p_environment);
+ } else if (is_environment(p_render_data->environment)) {
+ RS::EnvironmentBG bg_mode = environment_get_background(p_render_data->environment);
+ float bg_energy = environment_get_bg_energy(p_render_data->environment);
switch (bg_mode) {
case RS::ENV_BG_CLEAR_COLOR: {
clear_color = p_default_bg_color;
@@ -405,19 +393,19 @@ void RenderForwardMobile::_render_scene(RID p_render_buffer, const Transform &p_
clear_color.g *= bg_energy;
clear_color.b *= bg_energy;
/*
- if (render_buffers_has_volumetric_fog(p_render_buffer) || environment_is_fog_enabled(p_environment)) {
+ if (render_buffers_has_volumetric_fog(p_render_data->render_buffers) || environment_is_fog_enabled(p_render_data->environment)) {
draw_sky_fog_only = true;
storage->material_set_param(sky.sky_scene_state.fog_material, "clear_color", Variant(clear_color.to_linear()));
}
*/
} break;
case RS::ENV_BG_COLOR: {
- clear_color = environment_get_bg_color(p_environment);
+ clear_color = environment_get_bg_color(p_render_data->environment);
clear_color.r *= bg_energy;
clear_color.g *= bg_energy;
clear_color.b *= bg_energy;
/*
- if (render_buffers_has_volumetric_fog(p_render_buffer) || environment_is_fog_enabled(p_environment)) {
+ if (render_buffers_has_volumetric_fog(p_render_data->render_buffers) || environment_is_fog_enabled(p_render_data->environment)) {
draw_sky_fog_only = true;
storage->material_set_param(sky.sky_scene_state.fog_material, "clear_color", Variant(clear_color.to_linear()));
}
@@ -438,21 +426,21 @@ void RenderForwardMobile::_render_scene(RID p_render_buffer, const Transform &p_
}
}
// setup sky if used for ambient, reflections, or background
- if (draw_sky || draw_sky_fog_only || environment_get_reflection_source(p_environment) == RS::ENV_REFLECTION_SOURCE_SKY || environment_get_ambient_source(p_environment) == RS::ENV_AMBIENT_SOURCE_SKY) {
+ if (draw_sky || draw_sky_fog_only || environment_get_reflection_source(p_render_data->environment) == RS::ENV_REFLECTION_SOURCE_SKY || environment_get_ambient_source(p_render_data->environment) == RS::ENV_AMBIENT_SOURCE_SKY) {
RENDER_TIMESTAMP("Setup Sky");
RD::get_singleton()->draw_command_begin_label("Setup Sky");
- CameraMatrix projection = p_cam_projection;
- if (p_reflection_probe.is_valid()) {
+ CameraMatrix projection = p_render_data->cam_projection;
+ if (p_render_data->reflection_probe.is_valid()) {
CameraMatrix correction;
correction.set_depth_correction(true);
- projection = correction * p_cam_projection;
+ projection = correction * p_render_data->cam_projection;
}
- sky.setup(env, p_render_buffer, projection, p_cam_transform, screen_size, this);
+ sky.setup(env, p_render_data->render_buffers, projection, p_render_data->cam_transform, screen_size, this);
RID sky_rid = env->sky;
if (sky_rid.is_valid()) {
- sky.update(env, projection, p_cam_transform, time);
+ sky.update(env, projection, p_render_data->cam_transform, time);
radiance_texture = sky.sky_get_radiance_texture_rd(sky_rid);
} else {
// do not try to draw sky if invalid
@@ -468,17 +456,17 @@ void RenderForwardMobile::_render_scene(RID p_render_buffer, const Transform &p_
// !BAS! Look into this, seems most of the code in here related to clustered only, may want to move this code into ForwardClustered/RenderForwardMobile before calling it from here
// does trigger shadow map rendering so kinda important
- _pre_opaque_render(false, false, RID(), RID());
+ _pre_opaque_render(p_render_data, false, false, RID(), RID());
RD::get_singleton()->draw_command_begin_label("Render Opaque Pass");
- scene_state.ubo.directional_light_count = _get_render_state_directional_light_count();
+ scene_state.ubo.directional_light_count = p_render_data->directional_light_count;
- _setup_environment(p_environment, p_render_buffer, p_cam_projection, p_cam_transform, p_reflection_probe, p_reflection_probe.is_valid(), screen_size, p_shadow_atlas, !p_reflection_probe.is_valid(), p_default_bg_color, p_cam_projection.get_z_near(), p_cam_projection.get_z_far(), p_render_buffer.is_valid());
+ _setup_environment(p_render_data, p_render_data->reflection_probe.is_valid(), screen_size, !p_render_data->reflection_probe.is_valid(), p_default_bg_color, p_render_data->render_buffers.is_valid());
RENDER_TIMESTAMP("Render Opaque Pass");
- RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_OPAQUE, p_render_buffer, radiance_texture, p_shadow_atlas, p_reflection_atlas, p_lightmaps, true);
+ RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_OPAQUE, p_render_data, radiance_texture, true);
bool can_continue_color = !scene_state.used_screen_texture && !using_ssr && !using_sss;
bool can_continue_depth = !scene_state.used_depth_texture && !using_ssr && !using_sss;
@@ -491,7 +479,7 @@ void RenderForwardMobile::_render_scene(RID p_render_buffer, const Transform &p_
Vector<Color> c;
c.push_back(clear_color.to_linear());
- RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, PASS_MODE_COLOR, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), lod_camera_plane, lod_distance_multiplier, p_screen_lod_threshold);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, PASS_MODE_COLOR, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->lod_camera_plane, p_render_data->lod_distance_multiplier, p_render_data->screen_lod_threshold);
_render_list_with_threads(&render_list_params, opaque_framebuffer, keep_color ? RD::INITIAL_ACTION_KEEP : RD::INITIAL_ACTION_CLEAR, will_continue_color ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, will_continue_depth ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, c, 1.0, 0);
}
@@ -500,14 +488,14 @@ void RenderForwardMobile::_render_scene(RID p_render_buffer, const Transform &p_
if (draw_sky || draw_sky_fog_only) {
RENDER_TIMESTAMP("Render Sky");
- CameraMatrix projection = p_cam_projection;
- if (p_reflection_probe.is_valid()) {
+ CameraMatrix projection = p_render_data->cam_projection;
+ if (p_render_data->reflection_probe.is_valid()) {
CameraMatrix correction;
correction.set_depth_correction(true);
- projection = correction * p_cam_projection;
+ projection = correction * p_render_data->cam_projection;
}
RD::get_singleton()->draw_command_begin_label("Draw Sky");
- sky.draw(env, can_continue_color, can_continue_depth, opaque_framebuffer, projection, p_cam_transform, time);
+ sky.draw(env, can_continue_color, can_continue_depth, opaque_framebuffer, projection, p_render_data->cam_transform, time);
RD::get_singleton()->draw_command_end_label();
}
@@ -529,12 +517,12 @@ void RenderForwardMobile::_render_scene(RID p_render_buffer, const Transform &p_
RD::get_singleton()->draw_command_begin_label("Render Transparent Pass");
- rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_ALPHA, p_render_buffer, radiance_texture, p_shadow_atlas, p_reflection_atlas, p_lightmaps, true);
+ rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_ALPHA, p_render_data, radiance_texture, true);
- _setup_environment(p_environment, p_render_buffer, p_cam_projection, p_cam_transform, p_reflection_probe, p_reflection_probe.is_valid(), screen_size, p_shadow_atlas, !p_reflection_probe.is_valid(), p_default_bg_color, p_cam_projection.get_z_near(), p_cam_projection.get_z_far(), false);
+ _setup_environment(p_render_data, p_render_data->reflection_probe.is_valid(), screen_size, !p_render_data->reflection_probe.is_valid(), p_default_bg_color, false);
{
- RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), lod_camera_plane, lod_distance_multiplier, p_screen_lod_threshold);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->lod_camera_plane, p_render_data->lod_distance_multiplier, p_render_data->screen_lod_threshold);
_render_list_with_threads(&render_list_params, alpha_framebuffer, can_continue_color ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, can_continue_depth ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ);
}
@@ -564,18 +552,29 @@ void RenderForwardMobile::_render_shadow_append(RID p_framebuffer, const PagedAr
SceneState::ShadowPass shadow_pass;
+ RenderDataRD render_data;
+ render_data.cam_projection = p_projection;
+ render_data.cam_transform = p_transform;
+ render_data.z_near = 0.0;
+ render_data.z_far = p_zfar;
+ render_data.instances = &p_instances;
+ render_data.lod_camera_plane = p_camera_plane;
+ render_data.lod_distance_multiplier = p_lod_distance_multiplier;
+
scene_state.ubo.dual_paraboloid_side = p_use_dp_flip ? -1 : 1;
- _setup_environment(RID(), RID(), p_projection, p_transform, RID(), true, Vector2(1, 1), RID(), !p_flip_y, Color(), 0, p_zfar, false, p_use_pancake, shadow_pass_index);
+ _setup_environment(&render_data, true, Vector2(1, 1), !p_flip_y, Color(), false, p_use_pancake, shadow_pass_index);
if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_DISABLE_LOD) {
- p_screen_lod_threshold = 0.0;
+ render_data.screen_lod_threshold = 0.0;
+ } else {
+ render_data.screen_lod_threshold = p_screen_lod_threshold;
}
PassMode pass_mode = p_use_dp ? PASS_MODE_SHADOW_DP : PASS_MODE_SHADOW;
uint32_t render_list_from = render_list[RENDER_LIST_SECONDARY].elements.size();
- _fill_render_list(RENDER_LIST_SECONDARY, p_instances, pass_mode, p_projection, p_transform, p_camera_plane, p_lod_distance_multiplier, p_screen_lod_threshold, true);
+ _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode, true);
uint32_t render_list_size = render_list[RENDER_LIST_SECONDARY].elements.size() - render_list_from;
render_list[RENDER_LIST_SECONDARY].sort_by_key_range(render_list_from, render_list_size);
_fill_instance_data(RENDER_LIST_SECONDARY, render_list_from, render_list_size, false);
@@ -594,8 +593,8 @@ void RenderForwardMobile::_render_shadow_append(RID p_framebuffer, const PagedAr
shadow_pass.rp_uniform_set = RID(); //will be filled later when instance buffer is complete
shadow_pass.camera_plane = p_camera_plane;
- shadow_pass.screen_lod_threshold = p_screen_lod_threshold;
- shadow_pass.lod_distance_multiplier = p_lod_distance_multiplier;
+ shadow_pass.screen_lod_threshold = render_data.screen_lod_threshold;
+ shadow_pass.lod_distance_multiplier = render_data.lod_distance_multiplier;
shadow_pass.framebuffer = p_framebuffer;
shadow_pass.initial_depth_action = p_begin ? (p_clear_region ? RD::INITIAL_ACTION_CLEAR_REGION : RD::INITIAL_ACTION_CLEAR) : (p_clear_region ? RD::INITIAL_ACTION_CLEAR_REGION_CONTINUE : RD::INITIAL_ACTION_CONTINUE);
@@ -612,7 +611,7 @@ void RenderForwardMobile::_render_shadow_process() {
for (uint32_t i = 0; i < scene_state.shadow_passes.size(); i++) {
//render passes need to be configured after instance buffer is done, since they need the latest version
SceneState::ShadowPass &shadow_pass = scene_state.shadow_passes[i];
- shadow_pass.rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, RID(), RID(), RID(), RID(), PagedArray<RID>(), false, i);
+ shadow_pass.rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, nullptr, RID(), false, i);
}
RD::get_singleton()->draw_command_end_label();
@@ -645,14 +644,19 @@ void RenderForwardMobile::_render_material(const Transform &p_cam_transform, con
scene_state.ubo.dual_paraboloid_side = 0;
scene_state.ubo.material_uv2_mode = false;
- _setup_environment(RID(), RID(), p_cam_projection, p_cam_transform, RID(), true, Vector2(1, 1), RID(), false, Color(), 0, 0);
+ RenderDataRD render_data;
+ render_data.cam_projection = p_cam_projection;
+ render_data.cam_transform = p_cam_transform;
+ render_data.instances = &p_instances;
+
+ _setup_environment(&render_data, true, Vector2(1, 1), false, Color());
PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL;
- _fill_render_list(RENDER_LIST_SECONDARY, p_instances, pass_mode, p_cam_projection, p_cam_transform);
+ _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode);
render_list[RENDER_LIST_SECONDARY].sort_by_key();
_fill_instance_data(RENDER_LIST_SECONDARY);
- RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, RID(), RID(), RID(), RID(), PagedArray<RID>());
+ RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, nullptr, RID());
RENDER_TIMESTAMP("Render Material");
@@ -683,14 +687,17 @@ void RenderForwardMobile::_render_uv2(const PagedArray<GeometryInstance *> &p_in
scene_state.ubo.dual_paraboloid_side = 0;
scene_state.ubo.material_uv2_mode = true;
- _setup_environment(RID(), RID(), CameraMatrix(), Transform(), RID(), true, Vector2(1, 1), RID(), false, Color(), 0, 0);
+ RenderDataRD render_data;
+ render_data.instances = &p_instances;
+
+ _setup_environment(&render_data, true, Vector2(1, 1), false, Color());
PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL;
- _fill_render_list(RENDER_LIST_SECONDARY, p_instances, pass_mode, CameraMatrix(), Transform());
+ _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode);
render_list[RENDER_LIST_SECONDARY].sort_by_key();
_fill_instance_data(RENDER_LIST_SECONDARY);
- RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, RID(), RID(), RID(), RID(), PagedArray<RID>());
+ RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, nullptr, RID());
RENDER_TIMESTAMP("Render Material");
@@ -747,15 +754,22 @@ void RenderForwardMobile::_render_particle_collider_heightfield(RID p_fb, const
_update_render_base_uniform_set();
scene_state.ubo.dual_paraboloid_side = 0;
- _setup_environment(RID(), RID(), p_cam_projection, p_cam_transform, RID(), true, Vector2(1, 1), RID(), true, Color(), 0, p_cam_projection.get_z_far(), false, false);
+ RenderDataRD render_data;
+ render_data.cam_projection = p_cam_projection;
+ render_data.cam_transform = p_cam_transform;
+ render_data.z_near = 0.0;
+ render_data.z_far = p_cam_projection.get_z_far();
+ render_data.instances = &p_instances;
+
+ _setup_environment(&render_data, true, Vector2(1, 1), true, Color(), false, false);
PassMode pass_mode = PASS_MODE_SHADOW;
- _fill_render_list(RENDER_LIST_SECONDARY, p_instances, pass_mode, p_cam_projection, p_cam_transform);
+ _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode);
render_list[RENDER_LIST_SECONDARY].sort_by_key();
_fill_instance_data(RENDER_LIST_SECONDARY);
- RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, RID(), RID(), RID(), RID(), PagedArray<RID>());
+ RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_SECONDARY, nullptr, RID());
RENDER_TIMESTAMP("Render Collider Heightfield");
@@ -902,7 +916,7 @@ RID RenderForwardMobile::_render_buffers_get_normal_texture(RID p_render_buffers
return RID();
}
-void RenderForwardMobile::_fill_render_list(RenderListType p_render_list, const PagedArray<GeometryInstance *> &p_instances, PassMode p_pass_mode, const CameraMatrix &p_cam_projection, const Transform &p_cam_transform, const Plane &p_lod_plane, float p_lod_distance_multiplier, float p_screen_lod_threshold, bool p_append) {
+void RenderForwardMobile::_fill_render_list(RenderListType p_render_list, const RenderDataRD *p_render_data, PassMode p_pass_mode, bool p_append) {
if (p_render_list == RENDER_LIST_OPAQUE) {
scene_state.used_sss = false;
scene_state.used_screen_texture = false;
@@ -911,9 +925,9 @@ void RenderForwardMobile::_fill_render_list(RenderListType p_render_list, const
}
uint32_t lightmap_captures_used = 0;
- Plane near_plane(p_cam_transform.origin, -p_cam_transform.basis.get_axis(Vector3::AXIS_Z));
- near_plane.d += p_cam_projection.get_z_near();
- float z_max = p_cam_projection.get_z_far() - p_cam_projection.get_z_near();
+ Plane near_plane(p_render_data->cam_transform.origin, -p_render_data->cam_transform.basis.get_axis(Vector3::AXIS_Z));
+ near_plane.d += p_render_data->cam_projection.get_z_near();
+ float z_max = p_render_data->cam_projection.get_z_far() - p_render_data->cam_projection.get_z_near();
RenderList *rl = &render_list[p_render_list];
@@ -929,8 +943,8 @@ void RenderForwardMobile::_fill_render_list(RenderListType p_render_list, const
//fill list
- for (int i = 0; i < (int)p_instances.size(); i++) {
- GeometryInstanceForwardMobile *inst = static_cast<GeometryInstanceForwardMobile *>(p_instances[i]);
+ for (int i = 0; i < (int)p_render_data->instances->size(); i++) {
+ GeometryInstanceForwardMobile *inst = static_cast<GeometryInstanceForwardMobile *>((*p_render_data->instances)[i]);
Vector3 support_min = inst->transformed_aabb.get_support(-near_plane.normal);
inst->depth = near_plane.distance_to(support_min);
@@ -988,13 +1002,13 @@ void RenderForwardMobile::_fill_render_list(RenderListType p_render_list, const
// LOD
- if (p_screen_lod_threshold > 0.0 && storage->mesh_surface_has_lod(surf->surface)) {
+ if (p_render_data->screen_lod_threshold > 0.0 && storage->mesh_surface_has_lod(surf->surface)) {
//lod
- Vector3 lod_support_min = inst->transformed_aabb.get_support(-p_lod_plane.normal);
- Vector3 lod_support_max = inst->transformed_aabb.get_support(p_lod_plane.normal);
+ Vector3 lod_support_min = inst->transformed_aabb.get_support(-p_render_data->lod_camera_plane.normal);
+ Vector3 lod_support_max = inst->transformed_aabb.get_support(p_render_data->lod_camera_plane.normal);
- float distance_min = p_lod_plane.distance_to(lod_support_min);
- float distance_max = p_lod_plane.distance_to(lod_support_max);
+ float distance_min = p_render_data->lod_camera_plane.distance_to(lod_support_min);
+ float distance_max = p_render_data->lod_camera_plane.distance_to(lod_support_max);
float distance = 0.0;
@@ -1007,7 +1021,7 @@ void RenderForwardMobile::_fill_render_list(RenderListType p_render_list, const
distance = -distance_max;
}
- surf->lod_index = storage->mesh_surface_get_lod(surf->surface, inst->lod_model_scale * inst->lod_bias, distance * p_lod_distance_multiplier, p_screen_lod_threshold);
+ surf->lod_index = storage->mesh_surface_get_lod(surf->surface, inst->lod_model_scale * inst->lod_bias, distance * p_render_data->lod_distance_multiplier, p_render_data->screen_lod_threshold);
} else {
surf->lod_index = 0;
}
@@ -1058,25 +1072,25 @@ void RenderForwardMobile::_fill_render_list(RenderListType p_render_list, const
}
}
-void RenderForwardMobile::_setup_environment(RID p_environment, RID p_render_buffers, const CameraMatrix &p_cam_projection, const Transform &p_cam_transform, RID p_reflection_probe, bool p_no_fog, const Size2i &p_screen_size, RID p_shadow_atlas, bool p_flip_y, const Color &p_default_bg_color, float p_znear, float p_zfar, bool p_opaque_render_buffers, bool p_pancake_shadows, int p_index) {
+void RenderForwardMobile::_setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_opaque_render_buffers, bool p_pancake_shadows, int p_index) {
//!BAS! need to go through this and find out what we don't need anymore
// This populates our UBO with main scene data that is pushed into set 1
- //CameraMatrix projection = p_cam_projection;
+ //CameraMatrix projection = p_render_data->cam_projection;
//projection.flip_y(); // Vulkan and modern APIs use Y-Down
CameraMatrix correction;
correction.set_depth_correction(p_flip_y);
- CameraMatrix projection = correction * p_cam_projection;
+ CameraMatrix projection = correction * p_render_data->cam_projection;
//store camera into ubo
RendererStorageRD::store_camera(projection, scene_state.ubo.projection_matrix);
RendererStorageRD::store_camera(projection.inverse(), scene_state.ubo.inv_projection_matrix);
- RendererStorageRD::store_transform(p_cam_transform, scene_state.ubo.camera_matrix);
- RendererStorageRD::store_transform(p_cam_transform.affine_inverse(), scene_state.ubo.inv_camera_matrix);
+ RendererStorageRD::store_transform(p_render_data->cam_transform, scene_state.ubo.camera_matrix);
+ RendererStorageRD::store_transform(p_render_data->cam_transform.affine_inverse(), scene_state.ubo.inv_camera_matrix);
- scene_state.ubo.z_far = p_zfar;
- scene_state.ubo.z_near = p_znear;
+ scene_state.ubo.z_far = p_render_data->z_far;
+ scene_state.ubo.z_near = p_render_data->z_near;
scene_state.ubo.pancake_shadows = p_pancake_shadows;
@@ -1094,19 +1108,8 @@ void RenderForwardMobile::_setup_environment(RID p_environment, RID p_render_buf
scene_state.ubo.screen_pixel_size[0] = screen_pixel_size.x;
scene_state.ubo.screen_pixel_size[1] = screen_pixel_size.y;
- /*
- scene_state.ubo.cluster_shift = get_shift_from_power_of_2(p_cluster_size);
- scene_state.ubo.max_cluster_element_count_div_32 = p_max_cluster_elements / 32;
- {
- uint32_t cluster_screen_width = (p_screen_size.width - 1) / p_cluster_size + 1;
- uint32_t cluster_screen_height = (p_screen_size.height - 1) / p_cluster_size + 1;
- scene_state.ubo.cluster_type_size = cluster_screen_width * cluster_screen_height * (scene_state.ubo.max_cluster_element_count_div_32 + 32);
- scene_state.ubo.cluster_width = cluster_screen_width;
- }
- */
-
- if (p_shadow_atlas.is_valid()) {
- Vector2 sas = shadow_atlas_get_size(p_shadow_atlas);
+ if (p_render_data->shadow_atlas.is_valid()) {
+ Vector2 sas = shadow_atlas_get_size(p_render_data->shadow_atlas);
scene_state.ubo.shadow_atlas_pixel_size[0] = 1.0 / sas.x;
scene_state.ubo.shadow_atlas_pixel_size[1] = 1.0 / sas.y;
}
@@ -1124,22 +1127,22 @@ void RenderForwardMobile::_setup_environment(RID p_environment, RID p_render_buf
scene_state.ubo.volumetric_fog_enabled = false;
scene_state.ubo.fog_enabled = false;
- if (p_render_buffers.is_valid()) {
- RenderBufferDataForwardMobile *render_buffers = (RenderBufferDataForwardMobile *)render_buffers_get_data(p_render_buffers);
+ if (p_render_data->render_buffers.is_valid()) {
+ RenderBufferDataForwardMobile *render_buffers = (RenderBufferDataForwardMobile *)render_buffers_get_data(p_render_data->render_buffers);
if (render_buffers->msaa != RS::VIEWPORT_MSAA_DISABLED) {
scene_state.ubo.gi_upscale_for_msaa = true;
}
- if (render_buffers_has_volumetric_fog(p_render_buffers)) {
+ if (render_buffers_has_volumetric_fog(p_render_data->render_buffers)) {
scene_state.ubo.volumetric_fog_enabled = true;
- float fog_end = render_buffers_get_volumetric_fog_end(p_render_buffers);
+ float fog_end = render_buffers_get_volumetric_fog_end(p_render_data->render_buffers);
if (fog_end > 0.0) {
scene_state.ubo.volumetric_fog_inv_length = 1.0 / fog_end;
} else {
scene_state.ubo.volumetric_fog_inv_length = 1.0;
}
- float fog_detail_spread = render_buffers_get_volumetric_fog_detail_spread(p_render_buffers); //reverse lookup
+ float fog_detail_spread = render_buffers_get_volumetric_fog_detail_spread(p_render_data->render_buffers); //reverse lookup
if (fog_detail_spread > 0.0) {
scene_state.ubo.volumetric_fog_detail_spread = 1.0 / fog_detail_spread;
} else {
@@ -1160,18 +1163,18 @@ void RenderForwardMobile::_setup_environment(RID p_environment, RID p_render_buf
scene_state.ubo.use_reflection_cubemap = false;
scene_state.ubo.ssao_enabled = false;
- } else if (is_environment(p_environment)) {
- RS::EnvironmentBG env_bg = environment_get_background(p_environment);
- RS::EnvironmentAmbientSource ambient_src = environment_get_ambient_source(p_environment);
+ } else if (is_environment(p_render_data->environment)) {
+ RS::EnvironmentBG env_bg = environment_get_background(p_render_data->environment);
+ RS::EnvironmentAmbientSource ambient_src = environment_get_ambient_source(p_render_data->environment);
- float bg_energy = environment_get_bg_energy(p_environment);
+ float bg_energy = environment_get_bg_energy(p_render_data->environment);
scene_state.ubo.ambient_light_color_energy[3] = bg_energy;
- scene_state.ubo.ambient_color_sky_mix = environment_get_ambient_sky_contribution(p_environment);
+ scene_state.ubo.ambient_color_sky_mix = environment_get_ambient_sky_contribution(p_render_data->environment);
//ambient
if (ambient_src == RS::ENV_AMBIENT_SOURCE_BG && (env_bg == RS::ENV_BG_CLEAR_COLOR || env_bg == RS::ENV_BG_COLOR)) {
- Color color = env_bg == RS::ENV_BG_CLEAR_COLOR ? p_default_bg_color : environment_get_bg_color(p_environment);
+ Color color = env_bg == RS::ENV_BG_CLEAR_COLOR ? p_default_bg_color : environment_get_bg_color(p_render_data->environment);
color = color.to_linear();
scene_state.ubo.ambient_light_color_energy[0] = color.r * bg_energy;
@@ -1180,15 +1183,15 @@ void RenderForwardMobile::_setup_environment(RID p_environment, RID p_render_buf
scene_state.ubo.use_ambient_light = true;
scene_state.ubo.use_ambient_cubemap = false;
} else {
- float energy = environment_get_ambient_light_energy(p_environment);
- Color color = environment_get_ambient_light_color(p_environment);
+ float energy = environment_get_ambient_light_energy(p_render_data->environment);
+ Color color = environment_get_ambient_light_color(p_render_data->environment);
color = color.to_linear();
scene_state.ubo.ambient_light_color_energy[0] = color.r * energy;
scene_state.ubo.ambient_light_color_energy[1] = color.g * energy;
scene_state.ubo.ambient_light_color_energy[2] = color.b * energy;
- Basis sky_transform = environment_get_sky_orientation(p_environment);
- sky_transform = sky_transform.inverse() * p_cam_transform.basis;
+ Basis sky_transform = environment_get_sky_orientation(p_render_data->environment);
+ sky_transform = sky_transform.inverse() * p_render_data->cam_transform.basis;
RendererStorageRD::store_transform_3x3(sky_transform, scene_state.ubo.radiance_inverse_xform);
scene_state.ubo.use_ambient_cubemap = (ambient_src == RS::ENV_AMBIENT_SOURCE_BG && env_bg == RS::ENV_BG_SKY) || ambient_src == RS::ENV_AMBIENT_SOURCE_SKY;
@@ -1196,43 +1199,43 @@ void RenderForwardMobile::_setup_environment(RID p_environment, RID p_render_buf
}
//specular
- RS::EnvironmentReflectionSource ref_src = environment_get_reflection_source(p_environment);
+ RS::EnvironmentReflectionSource ref_src = environment_get_reflection_source(p_render_data->environment);
if ((ref_src == RS::ENV_REFLECTION_SOURCE_BG && env_bg == RS::ENV_BG_SKY) || ref_src == RS::ENV_REFLECTION_SOURCE_SKY) {
scene_state.ubo.use_reflection_cubemap = true;
} else {
scene_state.ubo.use_reflection_cubemap = false;
}
- scene_state.ubo.ssao_enabled = p_opaque_render_buffers && environment_is_ssao_enabled(p_environment);
- scene_state.ubo.ssao_ao_affect = environment_get_ssao_ao_affect(p_environment);
- scene_state.ubo.ssao_light_affect = environment_get_ssao_light_affect(p_environment);
+ scene_state.ubo.ssao_enabled = p_opaque_render_buffers && environment_is_ssao_enabled(p_render_data->environment);
+ scene_state.ubo.ssao_ao_affect = environment_get_ssao_ao_affect(p_render_data->environment);
+ scene_state.ubo.ssao_light_affect = environment_get_ssao_light_affect(p_render_data->environment);
- Color ao_color = environment_get_ao_color(p_environment).to_linear();
+ Color ao_color = environment_get_ao_color(p_render_data->environment).to_linear();
scene_state.ubo.ao_color[0] = ao_color.r;
scene_state.ubo.ao_color[1] = ao_color.g;
scene_state.ubo.ao_color[2] = ao_color.b;
scene_state.ubo.ao_color[3] = ao_color.a;
- scene_state.ubo.fog_enabled = environment_is_fog_enabled(p_environment);
- scene_state.ubo.fog_density = environment_get_fog_density(p_environment);
- scene_state.ubo.fog_height = environment_get_fog_height(p_environment);
- scene_state.ubo.fog_height_density = environment_get_fog_height_density(p_environment);
+ scene_state.ubo.fog_enabled = environment_is_fog_enabled(p_render_data->environment);
+ scene_state.ubo.fog_density = environment_get_fog_density(p_render_data->environment);
+ scene_state.ubo.fog_height = environment_get_fog_height(p_render_data->environment);
+ scene_state.ubo.fog_height_density = environment_get_fog_height_density(p_render_data->environment);
if (scene_state.ubo.fog_height_density >= 0.0001) {
scene_state.ubo.fog_height_density = 1.0 / scene_state.ubo.fog_height_density;
}
- scene_state.ubo.fog_aerial_perspective = environment_get_fog_aerial_perspective(p_environment);
+ scene_state.ubo.fog_aerial_perspective = environment_get_fog_aerial_perspective(p_render_data->environment);
- Color fog_color = environment_get_fog_light_color(p_environment).to_linear();
- float fog_energy = environment_get_fog_light_energy(p_environment);
+ Color fog_color = environment_get_fog_light_color(p_render_data->environment).to_linear();
+ float fog_energy = environment_get_fog_light_energy(p_render_data->environment);
scene_state.ubo.fog_light_color[0] = fog_color.r * fog_energy;
scene_state.ubo.fog_light_color[1] = fog_color.g * fog_energy;
scene_state.ubo.fog_light_color[2] = fog_color.b * fog_energy;
- scene_state.ubo.fog_sun_scatter = environment_get_fog_sun_scatter(p_environment);
+ scene_state.ubo.fog_sun_scatter = environment_get_fog_sun_scatter(p_render_data->environment);
} else {
- if (p_reflection_probe.is_valid() && storage->reflection_probe_is_interior(reflection_probe_instance_get_probe(p_reflection_probe))) {
+ if (p_render_data->reflection_probe.is_valid() && storage->reflection_probe_is_interior(reflection_probe_instance_get_probe(p_render_data->reflection_probe))) {
scene_state.ubo.use_ambient_light = false;
} else {
scene_state.ubo.use_ambient_light = true;
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
index 232ad0066b..bf911319f2 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
@@ -148,8 +148,8 @@ protected:
}
};
- RID _setup_render_pass_uniform_set(RenderListType p_render_list, RID p_render_buffers, RID p_radiance_texture, RID p_shadow_atlas, RID p_reflection_atlas, const PagedArray<RID> &p_lightmaps, bool p_use_directional_shadow_atlas = false, int p_index = 0);
- virtual void _render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, const PagedArray<GeometryInstance *> &p_instances, const PagedArray<RID> &p_gi_probes, const PagedArray<RID> &p_lightmaps, RID p_environment, RID p_cluster_buffer, uint32_t p_cluster_size, uint32_t p_cluster_max_elements, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_bg_color, float p_screen_lod_threshold);
+ RID _setup_render_pass_uniform_set(RenderListType p_render_list, const RenderDataRD *p_render_data, RID p_radiance_texture, bool p_use_directional_shadow_atlas = false, int p_index = 0);
+ virtual void _render_scene(RenderDataRD *p_render_data, const Color &p_default_bg_color);
virtual void _render_shadow_begin();
virtual void _render_shadow_append(RID p_framebuffer, const PagedArray<GeometryInstance *> &p_instances, const CameraMatrix &p_projection, const Transform &p_transform, float p_zfar, float p_bias, float p_normal_bias, bool p_use_dp, bool p_use_dp_flip, bool p_use_pancake, const Plane &p_camera_plane = Plane(), float p_lod_distance_multiplier = 0.0, float p_screen_lod_threshold = 0.0, const Rect2i &p_rect = Rect2i(), bool p_flip_y = false, bool p_clear_region = true, bool p_begin = true, bool p_end = true);
@@ -167,13 +167,13 @@ protected:
void _update_render_base_uniform_set();
virtual RID _render_buffers_get_normal_texture(RID p_render_buffers);
- void _fill_render_list(RenderListType p_render_list, const PagedArray<GeometryInstance *> &p_instances, PassMode p_pass_mode, const CameraMatrix &p_cam_projection, const Transform &p_cam_transform, const Plane &p_lod_camera_plane = Plane(), float p_lod_distance_multiplier = 0.0, float p_screen_lod_threshold = 0.0, bool p_append = false);
+ void _fill_render_list(RenderListType p_render_list, const RenderDataRD *p_render_data, PassMode p_pass_mode, bool p_append = false);
void _fill_instance_data(RenderListType p_render_list, uint32_t p_offset = 0, int32_t p_max_elements = -1, bool p_update_buffer = true);
// void _update_instance_data_buffer(RenderListType p_render_list);
static RenderForwardMobile *singleton;
- void _setup_environment(RID p_environment, RID p_render_buffers, const CameraMatrix &p_cam_projection, const Transform &p_cam_transform, RID p_reflection_probe, bool p_no_fog, const Size2i &p_screen_size, RID p_shadow_atlas, bool p_flip_y, const Color &p_default_bg_color, float p_znear, float p_zfar, bool p_opaque_render_buffers = false, bool p_pancake_shadows = false, int p_index = 0);
+ void _setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_opaque_render_buffers = false, bool p_pancake_shadows = false, int p_index = 0);
void _setup_lightmaps(const PagedArray<RID> &p_lightmaps, const Transform &p_cam_transform);
RID render_base_uniform_set;
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index 377b0fd72d..f448698976 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -705,286 +705,128 @@ void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, const Item
case Item::Command::TYPE_MESH:
case Item::Command::TYPE_MULTIMESH:
case Item::Command::TYPE_PARTICLES: {
- ERR_PRINT("FIXME: Mesh, MultiMesh and Particles render commands are unimplemented currently, they need to be ported to the 4.0 rendering architecture.");
-#ifndef _MSC_VER
-#warning Item::Command types for Mesh, MultiMesh and Particles need to be implemented.
-#endif
- // See #if 0'ed code below to port from GLES3.
- } break;
-
-#if 0
- case Item::Command::TYPE_MESH: {
- Item::CommandMesh *mesh = static_cast<Item::CommandMesh *>(c);
- _set_texture_rect_mode(false);
-
- RasterizerStorageGLES3::Texture *texture = _bind_canvas_texture(mesh->texture, mesh->normal_map);
-
- if (texture) {
- Size2 texpixel_size(1.0 / texture->width, 1.0 / texture->height);
- state.canvas_shader.set_uniform(CanvasShaderGLES3::COLOR_TEXPIXEL_SIZE, texpixel_size);
+ RID mesh;
+ RID mesh_instance;
+ RID texture;
+ Color modulate(1, 1, 1, 1);
+ float world_backup[6];
+ int instance_count = 1;
+
+ for (int j = 0; j < 6; j++) {
+ world_backup[j] = push_constant.world[j];
}
- state.canvas_shader.set_uniform(CanvasShaderGLES3::MODELVIEW_MATRIX, state.final_transform * mesh->transform);
-
- RasterizerStorageGLES3::Mesh *mesh_data = storage->mesh_owner.getornull(mesh->mesh);
- if (mesh_data) {
- for (int j = 0; j < mesh_data->surfaces.size(); j++) {
- RasterizerStorageGLES3::Surface *s = mesh_data->surfaces[j];
- // materials are ignored in 2D meshes, could be added but many things (ie, lighting mode, reading from screen, etc) would break as they are not meant be set up at this point of drawing
- glBindVertexArray(s->array_id);
-
- glVertexAttrib4f(RS::ARRAY_COLOR, mesh->modulate.r, mesh->modulate.g, mesh->modulate.b, mesh->modulate.a);
+ if (c->type == Item::Command::TYPE_MESH) {
+ const Item::CommandMesh *m = static_cast<const Item::CommandMesh *>(c);
+ mesh = m->mesh;
+ mesh_instance = m->mesh_instance;
+ texture = m->texture;
+ modulate = m->modulate;
+ _update_transform_2d_to_mat2x3(base_transform * m->transform, push_constant.world);
+ } else if (c->type == Item::Command::TYPE_MULTIMESH) {
+ const Item::CommandMultiMesh *mm = static_cast<const Item::CommandMultiMesh *>(c);
+ RID multimesh = mm->multimesh;
+ mesh = storage->multimesh_get_mesh(multimesh);
+ texture = mm->texture;
+
+ if (storage->multimesh_get_transform_format(multimesh) != RS::MULTIMESH_TRANSFORM_2D) {
+ break;
+ }
- if (s->index_array_len) {
- glDrawElements(gl_primitive[s->primitive], s->index_array_len, (s->array_len >= (1 << 16)) ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT, 0);
- } else {
- glDrawArrays(gl_primitive[s->primitive], 0, s->array_len);
- }
+ instance_count = storage->multimesh_get_instances_to_draw(multimesh);
- glBindVertexArray(0);
+ RID uniform_set = storage->multimesh_get_2d_uniform_set(multimesh, shader.default_version_rd_shader, TRANSFORMS_UNIFORM_SET);
+ RD::get_singleton()->draw_list_bind_uniform_set(p_draw_list, uniform_set, TRANSFORMS_UNIFORM_SET);
+ push_constant.flags |= 1; //multimesh, trails disabled
+ if (storage->multimesh_uses_colors(multimesh)) {
+ push_constant.flags |= FLAGS_INSTANCING_HAS_COLORS;
}
- }
- state.canvas_shader.set_uniform(CanvasShaderGLES3::MODELVIEW_MATRIX, state.final_transform);
+ if (storage->multimesh_uses_custom_data(multimesh)) {
+ push_constant.flags |= FLAGS_INSTANCING_HAS_CUSTOM_DATA;
+ }
+ } else if (c->type == Item::Command::TYPE_PARTICLES) {
+ const Item::CommandParticles *pt = static_cast<const Item::CommandParticles *>(c);
+ ERR_BREAK(storage->particles_get_mode(pt->particles) != RS::PARTICLES_MODE_2D);
+ if (storage->particles_is_inactive(pt->particles)) {
+ break;
+ }
+ int dpc = storage->particles_get_draw_passes(pt->particles);
+ if (dpc == 0) {
+ break; //nothing to draw
+ }
+ uint32_t divisor = 1;
+ instance_count = storage->particles_get_amount(pt->particles, divisor);
- } break;
- case Item::Command::TYPE_MULTIMESH: {
- Item::CommandMultiMesh *mmesh = static_cast<Item::CommandMultiMesh *>(c);
+ RID uniform_set = storage->particles_get_instance_buffer_uniform_set(pt->particles, shader.default_version_rd_shader, TRANSFORMS_UNIFORM_SET);
+ RD::get_singleton()->draw_list_bind_uniform_set(p_draw_list, uniform_set, TRANSFORMS_UNIFORM_SET);
- RasterizerStorageGLES3::MultiMesh *multi_mesh = storage->multimesh_owner.getornull(mmesh->multimesh);
+ push_constant.flags |= divisor;
+ instance_count /= divisor;
- if (!multi_mesh)
- break;
+ push_constant.flags |= FLAGS_INSTANCING_HAS_COLORS;
+ push_constant.flags |= FLAGS_INSTANCING_HAS_CUSTOM_DATA;
- RasterizerStorageGLES3::Mesh *mesh_data = storage->mesh_owner.getornull(multi_mesh->mesh);
+ mesh = storage->particles_get_draw_pass_mesh(pt->particles, 0); //higher ones are ignored
+ texture = pt->texture;
+ }
- if (!mesh_data)
+ if (mesh.is_null()) {
break;
+ }
- RasterizerStorageGLES3::Texture *texture = _bind_canvas_texture(mmesh->texture, mmesh->normal_map);
+ _bind_canvas_texture(p_draw_list, texture, current_filter, current_repeat, last_texture, push_constant, texpixel_size);
- state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_INSTANCE_CUSTOM, multi_mesh->custom_data_format != RS::MULTIMESH_CUSTOM_DATA_NONE);
- state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_INSTANCING, true);
- //reset shader and force rebind
- state.using_texture_rect = true;
- _set_texture_rect_mode(false);
+ uint32_t surf_count = storage->mesh_get_surface_count(mesh);
+ static const PipelineVariant variant[RS::PRIMITIVE_MAX] = { PIPELINE_VARIANT_ATTRIBUTE_POINTS, PIPELINE_VARIANT_ATTRIBUTE_LINES, PIPELINE_VARIANT_ATTRIBUTE_LINES_STRIP, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLES, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLE_STRIP };
- if (texture) {
- Size2 texpixel_size(1.0 / texture->width, 1.0 / texture->height);
- state.canvas_shader.set_uniform(CanvasShaderGLES3::COLOR_TEXPIXEL_SIZE, texpixel_size);
- }
+ push_constant.modulation[0] = base_color.r * modulate.r;
+ push_constant.modulation[1] = base_color.g * modulate.g;
+ push_constant.modulation[2] = base_color.b * modulate.b;
+ push_constant.modulation[3] = base_color.a * modulate.a;
- int amount = MIN(multi_mesh->size, multi_mesh->visible_instances);
-
- if (amount == -1) {
- amount = multi_mesh->size;
+ for (int j = 0; j < 4; j++) {
+ push_constant.src_rect[j] = 0;
+ push_constant.dst_rect[j] = 0;
+ push_constant.ninepatch_margins[j] = 0;
}
- for (int j = 0; j < mesh_data->surfaces.size(); j++) {
- RasterizerStorageGLES3::Surface *s = mesh_data->surfaces[j];
- // materials are ignored in 2D meshes, could be added but many things (ie, lighting mode, reading from screen, etc) would break as they are not meant be set up at this point of drawing
- glBindVertexArray(s->instancing_array_id);
+ for (uint32_t j = 0; j < surf_count; j++) {
+ void *surface = storage->mesh_get_surface(mesh, j);
- glBindBuffer(GL_ARRAY_BUFFER, multi_mesh->buffer); //modify the buffer
+ RS::PrimitiveType primitive = storage->mesh_surface_get_primitive(surface);
+ ERR_CONTINUE(primitive < 0 || primitive >= RS::PRIMITIVE_MAX);
- int stride = (multi_mesh->xform_floats + multi_mesh->color_floats + multi_mesh->custom_data_floats) * 4;
- glEnableVertexAttribArray(8);
- glVertexAttribPointer(8, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(0));
- glVertexAttribDivisor(8, 1);
- glEnableVertexAttribArray(9);
- glVertexAttribPointer(9, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(4 * 4));
- glVertexAttribDivisor(9, 1);
+ uint32_t input_mask = pipeline_variants->variants[light_mode][variant[primitive]].get_vertex_input_mask();
- int color_ofs;
+ RID vertex_array;
+ RD::VertexFormatID vertex_format = RD::INVALID_FORMAT_ID;
- if (multi_mesh->transform_format == RS::MULTIMESH_TRANSFORM_3D) {
- glEnableVertexAttribArray(10);
- glVertexAttribPointer(10, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(8 * 4));
- glVertexAttribDivisor(10, 1);
- color_ofs = 12 * 4;
+ if (mesh_instance.is_valid()) {
+ storage->mesh_instance_surface_get_vertex_arrays_and_format(mesh_instance, j, input_mask, vertex_array, vertex_format);
} else {
- glDisableVertexAttribArray(10);
- glVertexAttrib4f(10, 0, 0, 1, 0);
- color_ofs = 8 * 4;
+ storage->mesh_surface_get_vertex_arrays_and_format(surface, input_mask, vertex_array, vertex_format);
}
- int custom_data_ofs = color_ofs;
-
- switch (multi_mesh->color_format) {
- case RS::MULTIMESH_COLOR_NONE: {
- glDisableVertexAttribArray(11);
- glVertexAttrib4f(11, 1, 1, 1, 1);
- } break;
- case RS::MULTIMESH_COLOR_8BIT: {
- glEnableVertexAttribArray(11);
- glVertexAttribPointer(11, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, CAST_INT_TO_UCHAR_PTR(color_ofs));
- glVertexAttribDivisor(11, 1);
- custom_data_ofs += 4;
-
- } break;
- case RS::MULTIMESH_COLOR_FLOAT: {
- glEnableVertexAttribArray(11);
- glVertexAttribPointer(11, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(color_ofs));
- glVertexAttribDivisor(11, 1);
- custom_data_ofs += 4 * 4;
- } break;
- }
+ RID pipeline = pipeline_variants->variants[light_mode][variant[primitive]].get_render_pipeline(vertex_format, p_framebuffer_format);
+ RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
- switch (multi_mesh->custom_data_format) {
- case RS::MULTIMESH_CUSTOM_DATA_NONE: {
- glDisableVertexAttribArray(12);
- glVertexAttrib4f(12, 1, 1, 1, 1);
- } break;
- case RS::MULTIMESH_CUSTOM_DATA_8BIT: {
- glEnableVertexAttribArray(12);
- glVertexAttribPointer(12, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, CAST_INT_TO_UCHAR_PTR(custom_data_ofs));
- glVertexAttribDivisor(12, 1);
-
- } break;
- case RS::MULTIMESH_CUSTOM_DATA_FLOAT: {
- glEnableVertexAttribArray(12);
- glVertexAttribPointer(12, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(custom_data_ofs));
- glVertexAttribDivisor(12, 1);
- } break;
- }
+ RID index_array = storage->mesh_surface_get_index_array(surface, 0);
- if (s->index_array_len) {
- glDrawElementsInstanced(gl_primitive[s->primitive], s->index_array_len, (s->array_len >= (1 << 16)) ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT, 0, amount);
- } else {
- glDrawArraysInstanced(gl_primitive[s->primitive], 0, s->array_len, amount);
+ if (index_array.is_valid()) {
+ RD::get_singleton()->draw_list_bind_index_array(p_draw_list, index_array);
}
- glBindVertexArray(0);
- }
-
- state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_INSTANCE_CUSTOM, false);
- state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_INSTANCING, false);
- state.using_texture_rect = true;
- _set_texture_rect_mode(false);
-
- } break;
- case Item::Command::TYPE_PARTICLES: {
- Item::CommandParticles *particles_cmd = static_cast<Item::CommandParticles *>(c);
-
- RasterizerStorageGLES3::Particles *particles = storage->particles_owner.getornull(particles_cmd->particles);
- if (!particles)
- break;
-
- if (particles->inactive && !particles->emitting)
- break;
-
- glVertexAttrib4f(RS::ARRAY_COLOR, 1, 1, 1, 1); //not used, so keep white
-
- RenderingServerDefault::redraw_request();
-
- storage->particles_request_process(particles_cmd->particles);
- //enable instancing
-
- state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_INSTANCE_CUSTOM, true);
- state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_PARTICLES, true);
- state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_INSTANCING, true);
- //reset shader and force rebind
- state.using_texture_rect = true;
- _set_texture_rect_mode(false);
-
- RasterizerStorageGLES3::Texture *texture = _bind_canvas_texture(particles_cmd->texture, particles_cmd->normal_map);
-
- if (texture) {
- Size2 texpixel_size(1.0 / texture->width, 1.0 / texture->height);
- state.canvas_shader.set_uniform(CanvasShaderGLES3::COLOR_TEXPIXEL_SIZE, texpixel_size);
- } else {
- state.canvas_shader.set_uniform(CanvasShaderGLES3::COLOR_TEXPIXEL_SIZE, Vector2(1.0, 1.0));
- }
-
- if (!particles->use_local_coords) {
- Transform2D inv_xf;
- inv_xf.set_axis(0, Vector2(particles->emission_transform.basis.get_axis(0).x, particles->emission_transform.basis.get_axis(0).y));
- inv_xf.set_axis(1, Vector2(particles->emission_transform.basis.get_axis(1).x, particles->emission_transform.basis.get_axis(1).y));
- inv_xf.set_origin(Vector2(particles->emission_transform.get_origin().x, particles->emission_transform.get_origin().y));
- inv_xf.affine_invert();
+ RD::get_singleton()->draw_list_bind_vertex_array(p_draw_list, vertex_array);
+ RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
- state.canvas_shader.set_uniform(CanvasShaderGLES3::MODELVIEW_MATRIX, state.final_transform * inv_xf);
+ RD::get_singleton()->draw_list_draw(p_draw_list, index_array.is_valid(), instance_count);
}
- glBindVertexArray(data.particle_quad_array); //use particle quad array
- glBindBuffer(GL_ARRAY_BUFFER, particles->particle_buffers[0]); //bind particle buffer
-
- int stride = sizeof(float) * 4 * 6;
-
- int amount = particles->amount;
-
- if (particles->draw_order != RS::PARTICLES_DRAW_ORDER_LIFETIME) {
- glEnableVertexAttribArray(8); //xform x
- glVertexAttribPointer(8, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(sizeof(float) * 4 * 3));
- glVertexAttribDivisor(8, 1);
- glEnableVertexAttribArray(9); //xform y
- glVertexAttribPointer(9, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(sizeof(float) * 4 * 4));
- glVertexAttribDivisor(9, 1);
- glEnableVertexAttribArray(10); //xform z
- glVertexAttribPointer(10, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(sizeof(float) * 4 * 5));
- glVertexAttribDivisor(10, 1);
- glEnableVertexAttribArray(11); //color
- glVertexAttribPointer(11, 4, GL_FLOAT, GL_FALSE, stride, nullptr);
- glVertexAttribDivisor(11, 1);
- glEnableVertexAttribArray(12); //custom
- glVertexAttribPointer(12, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(sizeof(float) * 4 * 2));
- glVertexAttribDivisor(12, 1);
-
- glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, amount);
- } else {
- //split
- int split = int(Math::ceil(particles->phase * particles->amount));
-
- if (amount - split > 0) {
- glEnableVertexAttribArray(8); //xform x
- glVertexAttribPointer(8, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(stride * split + sizeof(float) * 4 * 3));
- glVertexAttribDivisor(8, 1);
- glEnableVertexAttribArray(9); //xform y
- glVertexAttribPointer(9, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(stride * split + sizeof(float) * 4 * 4));
- glVertexAttribDivisor(9, 1);
- glEnableVertexAttribArray(10); //xform z
- glVertexAttribPointer(10, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(stride * split + sizeof(float) * 4 * 5));
- glVertexAttribDivisor(10, 1);
- glEnableVertexAttribArray(11); //color
- glVertexAttribPointer(11, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(stride * split + 0));
- glVertexAttribDivisor(11, 1);
- glEnableVertexAttribArray(12); //custom
- glVertexAttribPointer(12, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(stride * split + sizeof(float) * 4 * 2));
- glVertexAttribDivisor(12, 1);
-
- glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, amount - split);
- }
-
- if (split > 0) {
- glEnableVertexAttribArray(8); //xform x
- glVertexAttribPointer(8, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(sizeof(float) * 4 * 3));
- glVertexAttribDivisor(8, 1);
- glEnableVertexAttribArray(9); //xform y
- glVertexAttribPointer(9, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(sizeof(float) * 4 * 4));
- glVertexAttribDivisor(9, 1);
- glEnableVertexAttribArray(10); //xform z
- glVertexAttribPointer(10, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(sizeof(float) * 4 * 5));
- glVertexAttribDivisor(10, 1);
- glEnableVertexAttribArray(11); //color
- glVertexAttribPointer(11, 4, GL_FLOAT, GL_FALSE, stride, nullptr);
- glVertexAttribDivisor(11, 1);
- glEnableVertexAttribArray(12); //custom
- glVertexAttribPointer(12, 4, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(sizeof(float) * 4 * 2));
- glVertexAttribDivisor(12, 1);
-
- glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, split);
- }
+ for (int j = 0; j < 6; j++) {
+ push_constant.world[j] = world_backup[j];
}
- glBindVertexArray(0);
-
- state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_INSTANCE_CUSTOM, false);
- state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_PARTICLES, false);
- state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_INSTANCING, false);
- state.using_texture_rect = true;
- _set_texture_rect_mode(false);
-
} break;
-#endif
case Item::Command::TYPE_TRANSFORM: {
const Item::CommandTransform *transform = static_cast<const Item::CommandTransform *>(c);
_update_transform_2d_to_mat2x3(base_transform * transform->xform, push_constant.world);
@@ -1437,6 +1279,8 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
Item *canvas_group_owner = nullptr;
+ bool update_skeletons = false;
+
while (ci) {
if (ci->copy_back_buffer && canvas_group_owner == nullptr) {
backbuffer_copy = true;
@@ -1472,9 +1316,27 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
}
}
+ if (ci->skeleton.is_valid()) {
+ const Item::Command *c = ci->commands;
+
+ while (c) {
+ if (c->type == Item::Command::TYPE_MESH) {
+ const Item::CommandMesh *cm = static_cast<const Item::CommandMesh *>(c);
+ if (cm->mesh_instance.is_valid()) {
+ storage->mesh_instance_check_for_update(cm->mesh_instance);
+ update_skeletons = true;
+ }
+ }
+ }
+ }
+
if (ci->canvas_group_owner != nullptr) {
if (canvas_group_owner == nullptr) {
//Canvas group begins here, render until before this item
+ if (update_skeletons) {
+ storage->update_mesh_instances();
+ update_skeletons = false;
+ }
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list);
item_count = 0;
@@ -1494,6 +1356,11 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
}
if (ci == canvas_group_owner) {
+ if (update_skeletons) {
+ storage->update_mesh_instances();
+ update_skeletons = false;
+ }
+
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, true);
item_count = 0;
@@ -1506,6 +1373,10 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
if (backbuffer_copy) {
//render anything pending, including clearing if no items
+ if (update_skeletons) {
+ storage->update_mesh_instances();
+ update_skeletons = false;
+ }
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list);
item_count = 0;
@@ -1518,6 +1389,11 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
items[item_count++] = ci;
if (!ci->next || item_count == MAX_RENDER_ITEMS - 1) {
+ if (update_skeletons) {
+ storage->update_mesh_instances();
+ update_skeletons = false;
+ }
+
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list);
//then reset
item_count = 0;
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
index cb947d7180..8129cc6c9b 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
@@ -67,12 +67,10 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
};
enum {
- FLAGS_INSTANCING_STRIDE_MASK = 0xF,
- FLAGS_INSTANCING_ENABLED = (1 << 4),
- FLAGS_INSTANCING_HAS_COLORS = (1 << 5),
- FLAGS_INSTANCING_COLOR_8BIT = (1 << 6),
- FLAGS_INSTANCING_HAS_CUSTOM_DATA = (1 << 7),
- FLAGS_INSTANCING_CUSTOM_DATA_8_BIT = (1 << 8),
+
+ FLAGS_INSTANCING_MASK = 0x7F,
+ FLAGS_INSTANCING_HAS_COLORS = (1 << 7),
+ FLAGS_INSTANCING_HAS_CUSTOM_DATA = (1 << 8),
FLAGS_CLIP_RECT_UV = (1 << 9),
FLAGS_TRANSPOSE_RECT = (1 << 10),
diff --git a/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp
index bc92e0b1ad..b289b17fad 100644
--- a/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp
@@ -1367,7 +1367,7 @@ void RendererSceneGIRD::SDFGI::debug_probes(RD::DrawListID p_draw_list, RID p_fr
}
}
-void RendererSceneGIRD::SDFGI::pre_process_gi(const Transform &p_transform, RendererSceneRenderRD *p_scene_render) {
+void RendererSceneGIRD::SDFGI::pre_process_gi(const Transform &p_transform, RenderDataRD *p_render_data, RendererSceneRenderRD *p_scene_render) {
/* Update general SDFGI Buffer */
SDFGIData sdfgi_data;
diff --git a/servers/rendering/renderer_rd/renderer_scene_gi_rd.h b/servers/rendering/renderer_rd/renderer_scene_gi_rd.h
index df20011b23..59f5f374d1 100644
--- a/servers/rendering/renderer_rd/renderer_scene_gi_rd.h
+++ b/servers/rendering/renderer_rd/renderer_scene_gi_rd.h
@@ -48,7 +48,8 @@
#include "servers/rendering/renderer_scene_render.h"
#include "servers/rendering/rendering_device.h"
-// Forward declare RendererSceneRenderRD so we can pass it into some of our methods, these classes are pretty tightly bound
+// Forward declare RenderDataRD and RendererSceneRenderRD so we can pass it into some of our methods, these classes are pretty tightly bound
+struct RenderDataRD;
class RendererSceneRenderRD;
class RendererSceneGIRD {
@@ -529,7 +530,7 @@ public:
void debug_draw(const CameraMatrix &p_projection, const Transform &p_transform, int p_width, int p_height, RID p_render_target, RID p_texture);
void debug_probes(RD::DrawListID p_draw_list, RID p_framebuffer, const CameraMatrix &p_camera_with_transform);
- void pre_process_gi(const Transform &p_transform, RendererSceneRenderRD *p_scene_render);
+ void pre_process_gi(const Transform &p_transform, RenderDataRD *p_render_data, RendererSceneRenderRD *p_scene_render);
void render_region(RID p_render_buffers, int p_region, const PagedArray<RendererSceneRender::GeometryInstance *> &p_instances, RendererSceneRenderRD *p_scene_render);
void render_static_lights(RID p_render_buffers, uint32_t p_cascade_count, const uint32_t *p_cascade_indices, const PagedArray<RID> *p_positional_light_cull_result, RendererSceneRenderRD *p_scene_render);
};
diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
index d7a5d1211c..1f01de1333 100644
--- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
@@ -1731,13 +1731,13 @@ void RendererSceneRenderRD::_process_ssao(RID p_render_buffers, RID p_environmen
storage->get_effects()->generate_ssao(rb->depth_texture, p_normal_buffer, rb->ssao.depth, rb->ssao.depth_slices, rb->ssao.ao_deinterleaved, rb->ssao.ao_deinterleaved_slices, rb->ssao.ao_pong, rb->ssao.ao_pong_slices, rb->ssao.ao_final, rb->ssao.importance_map[0], rb->ssao.importance_map[1], p_projection, settings, uniform_sets_are_invalid);
}
-void RendererSceneRenderRD::_render_buffers_post_process_and_tonemap(RID p_render_buffers, RID p_environment, RID p_camera_effects, const CameraMatrix &p_projection) {
- RenderBuffers *rb = render_buffers_owner.getornull(p_render_buffers);
+void RendererSceneRenderRD::_render_buffers_post_process_and_tonemap(const RenderDataRD *p_render_data) {
+ RenderBuffers *rb = render_buffers_owner.getornull(p_render_data->render_buffers);
ERR_FAIL_COND(!rb);
- RendererSceneEnvironmentRD *env = environment_owner.getornull(p_environment);
+ RendererSceneEnvironmentRD *env = environment_owner.getornull(p_render_data->environment);
//glow (if enabled)
- CameraEffects *camfx = camera_effects_owner.getornull(p_camera_effects);
+ CameraEffects *camfx = camera_effects_owner.getornull(p_render_data->camera_effects);
bool can_use_effects = rb->width >= 8 && rb->height >= 8;
@@ -1747,7 +1747,7 @@ void RendererSceneRenderRD::_render_buffers_post_process_and_tonemap(RID p_rende
}
float bokeh_size = camfx->dof_blur_amount * 64.0;
- storage->get_effects()->bokeh_dof(rb->texture, rb->depth_texture, Size2i(rb->width, rb->height), rb->blur[0].mipmaps[0].texture, rb->blur[1].mipmaps[0].texture, rb->blur[0].mipmaps[1].texture, camfx->dof_blur_far_enabled, camfx->dof_blur_far_distance, camfx->dof_blur_far_transition, camfx->dof_blur_near_enabled, camfx->dof_blur_near_distance, camfx->dof_blur_near_transition, bokeh_size, dof_blur_bokeh_shape, dof_blur_quality, dof_blur_use_jitter, p_projection.get_z_near(), p_projection.get_z_far(), p_projection.is_orthogonal());
+ storage->get_effects()->bokeh_dof(rb->texture, rb->depth_texture, Size2i(rb->width, rb->height), rb->blur[0].mipmaps[0].texture, rb->blur[1].mipmaps[0].texture, rb->blur[0].mipmaps[1].texture, camfx->dof_blur_far_enabled, camfx->dof_blur_far_distance, camfx->dof_blur_far_transition, camfx->dof_blur_near_enabled, camfx->dof_blur_near_distance, camfx->dof_blur_near_transition, bokeh_size, dof_blur_bokeh_shape, dof_blur_quality, dof_blur_use_jitter, p_render_data->z_near, p_render_data->z_far, p_render_data->cam_ortogonal);
}
if (can_use_effects && env && env->auto_exposure) {
@@ -3459,13 +3459,9 @@ void RendererSceneRenderRD::_update_volumetric_fog(RID p_render_buffers, RID p_e
rb->volumetric_fog->prev_cam_transform = p_cam_transform;
}
-uint32_t RendererSceneRenderRD::_get_render_state_directional_light_count() const {
- return render_state.directional_light_count;
-}
-
-bool RendererSceneRenderRD::_needs_post_prepass_render(bool p_use_gi) {
- if (render_state.render_buffers.is_valid()) {
- RenderBuffers *rb = render_buffers_owner.getornull(render_state.render_buffers);
+bool RendererSceneRenderRD::_needs_post_prepass_render(RenderDataRD *p_render_data, bool p_use_gi) {
+ if (p_render_data->render_buffers.is_valid()) {
+ RenderBuffers *rb = render_buffers_owner.getornull(p_render_data->render_buffers);
if (rb->sdfgi != nullptr) {
return true;
}
@@ -3473,34 +3469,34 @@ bool RendererSceneRenderRD::_needs_post_prepass_render(bool p_use_gi) {
return false;
}
-void RendererSceneRenderRD::_post_prepass_render(bool p_use_gi) {
- if (render_state.render_buffers.is_valid()) {
+void RendererSceneRenderRD::_post_prepass_render(RenderDataRD *p_render_data, bool p_use_gi) {
+ if (p_render_data->render_buffers.is_valid()) {
if (p_use_gi) {
- RenderBuffers *rb = render_buffers_owner.getornull(render_state.render_buffers);
+ RenderBuffers *rb = render_buffers_owner.getornull(p_render_data->render_buffers);
ERR_FAIL_COND(rb == nullptr);
if (rb->sdfgi == nullptr) {
return;
}
- RendererSceneEnvironmentRD *env = environment_owner.getornull(render_state.environment);
+ RendererSceneEnvironmentRD *env = environment_owner.getornull(p_render_data->environment);
rb->sdfgi->update_probes(env, sky.sky_owner.getornull(env->sky));
}
}
}
-void RendererSceneRenderRD::_pre_resolve_render(bool p_use_gi) {
- if (render_state.render_buffers.is_valid()) {
+void RendererSceneRenderRD::_pre_resolve_render(RenderDataRD *p_render_data, bool p_use_gi) {
+ if (p_render_data->render_buffers.is_valid()) {
if (p_use_gi) {
RD::get_singleton()->compute_list_end();
}
}
}
-void RendererSceneRenderRD::_pre_opaque_render(bool p_use_ssao, bool p_use_gi, RID p_normal_roughness_buffer, RID p_gi_probe_buffer) {
+void RendererSceneRenderRD::_pre_opaque_render(RenderDataRD *p_render_data, bool p_use_ssao, bool p_use_gi, RID p_normal_roughness_buffer, RID p_gi_probe_buffer) {
// Render shadows while GI is rendering, due to how barriers are handled, this should happen at the same time
- if (render_state.render_buffers.is_valid() && p_use_gi) {
- RenderBuffers *rb = render_buffers_owner.getornull(render_state.render_buffers);
+ if (p_render_data->render_buffers.is_valid() && p_use_gi) {
+ RenderBuffers *rb = render_buffers_owner.getornull(p_render_data->render_buffers);
ERR_FAIL_COND(rb == nullptr);
if (rb->sdfgi == nullptr) {
return;
@@ -3513,8 +3509,8 @@ void RendererSceneRenderRD::_pre_opaque_render(bool p_use_ssao, bool p_use_gi, R
render_state.shadows.clear();
render_state.directional_shadows.clear();
- Plane camera_plane(render_state.cam_transform.origin, -render_state.cam_transform.basis.get_axis(Vector3::AXIS_Z));
- float lod_distance_multiplier = render_state.cam_projection.get_lod_multiplier();
+ Plane camera_plane(p_render_data->cam_transform.origin, -p_render_data->cam_transform.basis.get_axis(Vector3::AXIS_Z));
+ float lod_distance_multiplier = p_render_data->cam_projection.get_lod_multiplier();
{
for (int i = 0; i < render_state.render_shadow_count; i++) {
@@ -3531,7 +3527,7 @@ void RendererSceneRenderRD::_pre_opaque_render(bool p_use_ssao, bool p_use_gi, R
//cube shadows are rendered in their own way
for (uint32_t i = 0; i < render_state.cube_shadows.size(); i++) {
- _render_shadow_pass(render_state.render_shadows[render_state.cube_shadows[i]].light, render_state.shadow_atlas, render_state.render_shadows[render_state.cube_shadows[i]].pass, render_state.render_shadows[render_state.cube_shadows[i]].instances, camera_plane, lod_distance_multiplier, render_state.screen_lod_threshold, true, true, true);
+ _render_shadow_pass(render_state.render_shadows[render_state.cube_shadows[i]].light, p_render_data->shadow_atlas, render_state.render_shadows[render_state.cube_shadows[i]].pass, render_state.render_shadows[render_state.cube_shadows[i]].instances, camera_plane, lod_distance_multiplier, p_render_data->screen_lod_threshold, true, true, true);
}
if (render_state.directional_shadows.size()) {
@@ -3545,7 +3541,7 @@ void RendererSceneRenderRD::_pre_opaque_render(bool p_use_ssao, bool p_use_gi, R
// Render GI
bool render_shadows = render_state.directional_shadows.size() || render_state.shadows.size();
- bool render_gi = render_state.render_buffers.is_valid() && p_use_gi;
+ bool render_gi = p_render_data->render_buffers.is_valid() && p_use_gi;
if (render_shadows && render_gi) {
RENDER_TIMESTAMP("Render GI + Render Shadows (parallel)");
@@ -3561,11 +3557,11 @@ void RendererSceneRenderRD::_pre_opaque_render(bool p_use_ssao, bool p_use_gi, R
//render directional shadows
for (uint32_t i = 0; i < render_state.directional_shadows.size(); i++) {
- _render_shadow_pass(render_state.render_shadows[render_state.directional_shadows[i]].light, render_state.shadow_atlas, render_state.render_shadows[render_state.directional_shadows[i]].pass, render_state.render_shadows[render_state.directional_shadows[i]].instances, camera_plane, lod_distance_multiplier, render_state.screen_lod_threshold, false, i == render_state.directional_shadows.size() - 1, false);
+ _render_shadow_pass(render_state.render_shadows[render_state.directional_shadows[i]].light, p_render_data->shadow_atlas, render_state.render_shadows[render_state.directional_shadows[i]].pass, render_state.render_shadows[render_state.directional_shadows[i]].instances, camera_plane, lod_distance_multiplier, p_render_data->screen_lod_threshold, false, i == render_state.directional_shadows.size() - 1, false);
}
//render positional shadows
for (uint32_t i = 0; i < render_state.shadows.size(); i++) {
- _render_shadow_pass(render_state.render_shadows[render_state.shadows[i]].light, render_state.shadow_atlas, render_state.render_shadows[render_state.shadows[i]].pass, render_state.render_shadows[render_state.shadows[i]].instances, camera_plane, lod_distance_multiplier, render_state.screen_lod_threshold, i == 0, i == render_state.shadows.size() - 1, true);
+ _render_shadow_pass(render_state.render_shadows[render_state.shadows[i]].light, p_render_data->shadow_atlas, render_state.render_shadows[render_state.shadows[i]].pass, render_state.render_shadows[render_state.shadows[i]].instances, camera_plane, lod_distance_multiplier, p_render_data->screen_lod_threshold, i == 0, i == render_state.shadows.size() - 1, true);
}
_render_shadow_process();
@@ -3573,7 +3569,7 @@ void RendererSceneRenderRD::_pre_opaque_render(bool p_use_ssao, bool p_use_gi, R
//start GI
if (render_gi) {
- gi.process_gi(render_state.render_buffers, p_normal_roughness_buffer, p_gi_probe_buffer, render_state.environment, render_state.cam_projection, render_state.cam_transform, *render_state.gi_probes, this);
+ gi.process_gi(p_render_data->render_buffers, p_normal_roughness_buffer, p_gi_probe_buffer, p_render_data->environment, p_render_data->cam_projection, p_render_data->cam_transform, *p_render_data->gi_probes, this);
}
//Do shadow rendering (in parallel with GI)
@@ -3585,9 +3581,9 @@ void RendererSceneRenderRD::_pre_opaque_render(bool p_use_ssao, bool p_use_gi, R
RD::get_singleton()->compute_list_end(RD::BARRIER_MASK_NO_BARRIER); //use a later barrier
}
- if (render_state.render_buffers.is_valid()) {
+ if (p_render_data->render_buffers.is_valid()) {
if (p_use_ssao) {
- _process_ssao(render_state.render_buffers, render_state.environment, p_normal_roughness_buffer, render_state.cam_projection);
+ _process_ssao(p_render_data->render_buffers, p_render_data->environment, p_normal_roughness_buffer, p_render_data->cam_projection);
}
}
@@ -3595,32 +3591,32 @@ void RendererSceneRenderRD::_pre_opaque_render(bool p_use_ssao, bool p_use_gi, R
RD::get_singleton()->barrier(RD::BARRIER_MASK_ALL, RD::BARRIER_MASK_ALL);
if (current_cluster_builder) {
- current_cluster_builder->begin(render_state.cam_transform, render_state.cam_projection, !render_state.reflection_probe.is_valid());
+ current_cluster_builder->begin(p_render_data->cam_transform, p_render_data->cam_projection, !p_render_data->reflection_probe.is_valid());
}
bool using_shadows = true;
- if (render_state.reflection_probe.is_valid()) {
- if (!storage->reflection_probe_renders_shadows(reflection_probe_instance_get_probe(render_state.reflection_probe))) {
+ if (p_render_data->reflection_probe.is_valid()) {
+ if (!storage->reflection_probe_renders_shadows(reflection_probe_instance_get_probe(p_render_data->reflection_probe))) {
using_shadows = false;
}
} else {
//do not render reflections when rendering a reflection probe
- _setup_reflections(*render_state.reflection_probes, render_state.cam_transform.affine_inverse(), render_state.environment);
+ _setup_reflections(*p_render_data->reflection_probes, p_render_data->cam_transform.affine_inverse(), p_render_data->environment);
}
uint32_t directional_light_count = 0;
uint32_t positional_light_count = 0;
- _setup_lights(*render_state.lights, render_state.cam_transform, render_state.shadow_atlas, using_shadows, directional_light_count, positional_light_count);
- _setup_decals(*render_state.decals, render_state.cam_transform.affine_inverse());
+ _setup_lights(*p_render_data->lights, p_render_data->cam_transform, p_render_data->shadow_atlas, using_shadows, directional_light_count, positional_light_count);
+ _setup_decals(*p_render_data->decals, p_render_data->cam_transform.affine_inverse());
- render_state.directional_light_count = directional_light_count;
+ p_render_data->directional_light_count = directional_light_count;
if (current_cluster_builder) {
current_cluster_builder->bake_cluster();
}
- if (render_state.render_buffers.is_valid()) {
+ if (p_render_data->render_buffers.is_valid()) {
bool directional_shadows = false;
for (uint32_t i = 0; i < directional_light_count; i++) {
if (cluster.directional_lights[i].shadow_enabled) {
@@ -3629,7 +3625,7 @@ void RendererSceneRenderRD::_pre_opaque_render(bool p_use_ssao, bool p_use_gi, R
}
}
if (is_volumetric_supported()) {
- _update_volumetric_fog(render_state.render_buffers, render_state.environment, render_state.cam_projection, render_state.cam_transform, render_state.shadow_atlas, directional_light_count, directional_shadows, positional_light_count, render_state.gi_probe_count);
+ _update_volumetric_fog(p_render_data->render_buffers, p_render_data->environment, p_render_data->cam_projection, p_render_data->cam_transform, p_render_data->shadow_atlas, directional_light_count, directional_shadows, positional_light_count, render_state.gi_probe_count);
}
}
}
@@ -3643,24 +3639,37 @@ void RendererSceneRenderRD::render_scene(RID p_render_buffers, const Transform &
}
//assign render data
+ RenderDataRD render_data;
{
- render_state.render_buffers = p_render_buffers;
- render_state.cam_transform = p_cam_transform;
- render_state.cam_projection = p_cam_projection;
- render_state.cam_ortogonal = p_cam_projection.is_orthogonal();
- render_state.instances = &p_instances;
- render_state.lights = &p_lights;
- render_state.reflection_probes = &p_reflection_probes;
- render_state.gi_probes = &p_gi_probes;
- render_state.decals = &p_decals;
- render_state.lightmaps = &p_lightmaps;
- render_state.environment = p_environment;
- render_state.camera_effects = p_camera_effects;
- render_state.shadow_atlas = p_shadow_atlas;
- render_state.reflection_atlas = p_reflection_atlas;
- render_state.reflection_probe = p_reflection_probe;
- render_state.reflection_probe_pass = p_reflection_probe_pass;
- render_state.screen_lod_threshold = p_screen_lod_threshold;
+ render_data.render_buffers = p_render_buffers;
+
+ render_data.cam_transform = p_cam_transform;
+ render_data.cam_projection = p_cam_projection;
+ render_data.cam_ortogonal = p_cam_projection.is_orthogonal(); // !BAS! Shouldn't this be p_cam_ortogonal ?
+ render_data.z_near = p_cam_projection.get_z_near();
+ render_data.z_far = p_cam_projection.get_z_far();
+
+ render_data.instances = &p_instances;
+ render_data.lights = &p_lights;
+ render_data.reflection_probes = &p_reflection_probes;
+ render_data.gi_probes = &p_gi_probes;
+ render_data.decals = &p_decals;
+ render_data.lightmaps = &p_lightmaps;
+ render_data.environment = p_environment;
+ render_data.camera_effects = p_camera_effects;
+ render_data.shadow_atlas = p_shadow_atlas;
+ render_data.reflection_atlas = p_reflection_atlas;
+ render_data.reflection_probe = p_reflection_probe;
+ render_data.reflection_probe_pass = p_reflection_probe_pass;
+
+ render_data.lod_distance_multiplier = p_cam_projection.get_lod_multiplier();
+ render_data.lod_camera_plane = Plane(p_cam_transform.get_origin(), -p_cam_transform.basis.get_axis(Vector3::AXIS_Z));
+
+ if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_DISABLE_LOD) {
+ render_data.screen_lod_threshold = 0.0;
+ } else {
+ render_data.screen_lod_threshold = p_screen_lod_threshold;
+ }
render_state.render_shadows = p_render_shadows;
render_state.render_shadow_count = p_render_shadow_count;
@@ -3672,9 +3681,9 @@ void RendererSceneRenderRD::render_scene(RID p_render_buffers, const Transform &
PagedArray<RID> empty;
if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_UNSHADED) {
- render_state.lights = &empty;
- render_state.reflection_probes = &empty;
- render_state.gi_probes = &empty;
+ render_data.lights = &empty;
+ render_data.reflection_probes = &empty;
+ render_data.gi_probes = &empty;
}
//sdfgi first
@@ -3704,11 +3713,11 @@ void RendererSceneRenderRD::render_scene(RID p_render_buffers, const Transform &
}
}
- if (render_buffers_owner.owns(render_state.render_buffers)) {
- // render_state.render_buffers == p_render_buffers so we can use our already retrieved rb
+ if (render_buffers_owner.owns(render_data.render_buffers)) {
+ // render_data.render_buffers == p_render_buffers so we can use our already retrieved rb
current_cluster_builder = rb->cluster_builder;
- } else if (reflection_probe_instance_owner.owns(render_state.reflection_probe)) {
- ReflectionProbeInstance *rpi = reflection_probe_instance_owner.getornull(render_state.reflection_probe);
+ } else if (reflection_probe_instance_owner.owns(render_data.reflection_probe)) {
+ ReflectionProbeInstance *rpi = reflection_probe_instance_owner.getornull(render_data.reflection_probe);
ReflectionAtlas *ra = reflection_atlas_owner.getornull(rpi->atlas);
if (!ra) {
ERR_PRINT("reflection probe has no reflection atlas! Bug?");
@@ -3724,12 +3733,12 @@ void RendererSceneRenderRD::render_scene(RID p_render_buffers, const Transform &
if (rb != nullptr && rb->sdfgi != nullptr) {
rb->sdfgi->update_cascades();
- rb->sdfgi->pre_process_gi(p_cam_transform, this);
+ rb->sdfgi->pre_process_gi(p_cam_transform, &render_data, this);
}
render_state.gi_probe_count = 0;
if (rb != nullptr && rb->sdfgi != nullptr) {
- gi.setup_giprobes(render_state.render_buffers, render_state.cam_transform, *render_state.gi_probes, render_state.gi_probe_count, this);
+ gi.setup_giprobes(render_data.render_buffers, render_data.cam_transform, *render_data.gi_probes, render_state.gi_probe_count, this);
rb->sdfgi->update_light();
}
@@ -3737,11 +3746,13 @@ void RendererSceneRenderRD::render_scene(RID p_render_buffers, const Transform &
render_state.depth_prepass_used = false;
//calls _pre_opaque_render between depth pre-pass and opaque pass
if (current_cluster_builder != nullptr) {
- _render_scene(p_render_buffers, p_cam_transform, p_cam_projection, p_cam_ortogonal, p_instances, *render_state.gi_probes, p_lightmaps, p_environment, current_cluster_builder->get_cluster_buffer(), current_cluster_builder->get_cluster_size(), current_cluster_builder->get_max_cluster_elements(), p_camera_effects, p_shadow_atlas, p_reflection_atlas, p_reflection_probe, p_reflection_probe_pass, clear_color, p_screen_lod_threshold);
- } else {
- _render_scene(p_render_buffers, p_cam_transform, p_cam_projection, p_cam_ortogonal, p_instances, *render_state.gi_probes, p_lightmaps, p_environment, RID(), 0, 0, p_camera_effects, p_shadow_atlas, p_reflection_atlas, p_reflection_probe, p_reflection_probe_pass, clear_color, p_screen_lod_threshold);
+ render_data.cluster_buffer = current_cluster_builder->get_cluster_buffer();
+ render_data.cluster_size = current_cluster_builder->get_cluster_size();
+ render_data.cluster_max_elements = current_cluster_builder->get_max_cluster_elements();
}
+ _render_scene(&render_data, clear_color);
+
if (p_render_buffers.is_valid()) {
if (debug_draw == RS::VIEWPORT_DEBUG_DRAW_CLUSTER_OMNI_LIGHTS || debug_draw == RS::VIEWPORT_DEBUG_DRAW_CLUSTER_SPOT_LIGHTS || debug_draw == RS::VIEWPORT_DEBUG_DRAW_CLUSTER_DECALS || debug_draw == RS::VIEWPORT_DEBUG_DRAW_CLUSTER_REFLECTION_PROBES) {
ClusterBuilderRD::ElementType elem_type = ClusterBuilderRD::ELEMENT_TYPE_MAX;
@@ -3768,7 +3779,7 @@ void RendererSceneRenderRD::render_scene(RID p_render_buffers, const Transform &
RENDER_TIMESTAMP("Tonemap");
- _render_buffers_post_process_and_tonemap(p_render_buffers, p_environment, p_camera_effects, p_cam_projection);
+ _render_buffers_post_process_and_tonemap(&render_data);
_render_buffers_debug_draw(p_render_buffers, p_shadow_atlas, p_occluder_debug_tex);
if (debug_draw == RS::VIEWPORT_DEBUG_DRAW_SDFGI && rb != nullptr && rb->sdfgi != nullptr) {
rb->sdfgi->debug_draw(p_cam_projection, p_cam_transform, rb->width, rb->height, rb->render_target, rb->texture);
diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.h b/servers/rendering/renderer_rd/renderer_scene_render_rd.h
index 7600d6823e..1f82ae6dec 100644
--- a/servers/rendering/renderer_rd/renderer_scene_render_rd.h
+++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.h
@@ -43,6 +43,40 @@
#include "servers/rendering/renderer_scene_render.h"
#include "servers/rendering/rendering_device.h"
+struct RenderDataRD {
+ RID render_buffers = RID();
+
+ Transform cam_transform = Transform();
+ CameraMatrix cam_projection = CameraMatrix();
+ bool cam_ortogonal = false;
+
+ float z_near = 0.0;
+ float z_far = 0.0;
+
+ const PagedArray<RendererSceneRender::GeometryInstance *> *instances = nullptr;
+ const PagedArray<RID> *lights = nullptr;
+ const PagedArray<RID> *reflection_probes = nullptr;
+ const PagedArray<RID> *gi_probes = nullptr;
+ const PagedArray<RID> *decals = nullptr;
+ const PagedArray<RID> *lightmaps = nullptr;
+ RID environment = RID();
+ RID camera_effects = RID();
+ RID shadow_atlas = RID();
+ RID reflection_atlas = RID();
+ RID reflection_probe = RID();
+ int reflection_probe_pass = 0;
+
+ float lod_distance_multiplier = 0.0;
+ Plane lod_camera_plane = Plane();
+ float screen_lod_threshold = 0.0;
+
+ RID cluster_buffer = RID();
+ uint32_t cluster_size = 0;
+ uint32_t cluster_max_elements = 0;
+
+ uint32_t directional_light_count = 0;
+};
+
class RendererSceneRenderRD : public RendererSceneRender {
friend RendererSceneSkyRD;
friend RendererSceneGIRD;
@@ -62,7 +96,7 @@ protected:
void _setup_decals(const PagedArray<RID> &p_decals, const Transform &p_camera_inverse_xform);
void _setup_reflections(const PagedArray<RID> &p_reflections, const Transform &p_camera_inverse_transform, RID p_environment);
- virtual void _render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, const PagedArray<GeometryInstance *> &p_instances, const PagedArray<RID> &p_gi_probes, const PagedArray<RID> &p_lightmaps, RID p_environment, RID p_cluster_buffer, uint32_t p_cluster_size, uint32_t p_cluster_max_elements, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_color, float p_screen_lod_threshold) = 0;
+ virtual void _render_scene(RenderDataRD *p_render_data, const Color &p_default_color) = 0;
virtual void _render_shadow_begin() = 0;
virtual void _render_shadow_append(RID p_framebuffer, const PagedArray<GeometryInstance *> &p_instances, const CameraMatrix &p_projection, const Transform &p_transform, float p_zfar, float p_bias, float p_normal_bias, bool p_use_dp, bool p_use_dp_flip, bool p_use_pancake, const Plane &p_camera_plane = Plane(), float p_lod_distance_multiplier = 0.0, float p_screen_lod_threshold = 0.0, const Rect2i &p_rect = Rect2i(), bool p_flip_y = false, bool p_clear_region = true, bool p_begin = true, bool p_end = true) = 0;
@@ -85,12 +119,11 @@ protected:
void _process_ssr(RID p_render_buffers, RID p_dest_framebuffer, RID p_normal_buffer, RID p_specular_buffer, RID p_metallic, const Color &p_metallic_mask, RID p_environment, const CameraMatrix &p_projection, bool p_use_additive);
void _process_sss(RID p_render_buffers, const CameraMatrix &p_camera);
- bool _needs_post_prepass_render(bool p_use_gi);
- void _post_prepass_render(bool p_use_gi);
- void _pre_resolve_render(bool p_use_gi);
+ bool _needs_post_prepass_render(RenderDataRD *p_render_data, bool p_use_gi);
+ void _post_prepass_render(RenderDataRD *p_render_data, bool p_use_gi);
+ void _pre_resolve_render(RenderDataRD *p_render_data, bool p_use_gi);
- void _pre_opaque_render(bool p_use_ssao, bool p_use_gi, RID p_normal_roughness_buffer, RID p_gi_probe_buffer);
- uint32_t _get_render_state_directional_light_count() const;
+ void _pre_opaque_render(RenderDataRD *p_render_data, bool p_use_ssao, bool p_use_gi, RID p_normal_roughness_buffer, RID p_gi_probe_buffer);
// needed for a single argument calls (material and uv2)
PagedArrayPool<GeometryInstance *> cull_argument_pool;
@@ -445,7 +478,7 @@ private:
void _allocate_luminance_textures(RenderBuffers *rb);
void _render_buffers_debug_draw(RID p_render_buffers, RID p_shadow_atlas, RID p_occlusion_buffer);
- void _render_buffers_post_process_and_tonemap(RID p_render_buffers, RID p_environment, RID p_camera_effects, const CameraMatrix &p_projection);
+ void _render_buffers_post_process_and_tonemap(const RenderDataRD *p_render_data);
/* Cluster */
@@ -592,38 +625,19 @@ private:
} cluster;
struct RenderState {
- RID render_buffers;
- Transform cam_transform;
- CameraMatrix cam_projection;
- bool cam_ortogonal = false;
- const PagedArray<GeometryInstance *> *instances = nullptr;
- const PagedArray<RID> *lights = nullptr;
- const PagedArray<RID> *reflection_probes = nullptr;
- const PagedArray<RID> *gi_probes = nullptr;
- const PagedArray<RID> *decals = nullptr;
- const PagedArray<RID> *lightmaps = nullptr;
- RID environment;
- RID camera_effects;
- RID shadow_atlas;
- RID reflection_atlas;
- RID reflection_probe;
- int reflection_probe_pass = 0;
- float screen_lod_threshold = 0.0;
-
- const RenderShadowData *render_shadows = nullptr;
+ const RendererSceneRender::RenderShadowData *render_shadows = nullptr;
int render_shadow_count = 0;
- const RenderSDFGIData *render_sdfgi_regions = nullptr;
+ const RendererSceneRender::RenderSDFGIData *render_sdfgi_regions = nullptr;
int render_sdfgi_region_count = 0;
- const RenderSDFGIUpdateData *sdfgi_update_data = nullptr;
+ const RendererSceneRender::RenderSDFGIUpdateData *sdfgi_update_data = nullptr;
- uint32_t directional_light_count = 0;
uint32_t gi_probe_count = 0;
LocalVector<int> cube_shadows;
LocalVector<int> shadows;
LocalVector<int> directional_shadows;
- bool depth_prepass_used;
+ bool depth_prepass_used; // this does not seem used anywhere...
} render_state;
struct VolumetricFog {
diff --git a/servers/rendering/renderer_rd/renderer_storage_rd.cpp b/servers/rendering/renderer_rd/renderer_storage_rd.cpp
index 47b9e33ca6..f419875d58 100644
--- a/servers/rendering/renderer_rd/renderer_storage_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_storage_rd.cpp
@@ -3873,6 +3873,18 @@ void RendererStorageRD::particles_initialize(RID p_rid) {
particles_owner.initialize_rid(p_rid, Particles());
}
+void RendererStorageRD::particles_set_mode(RID p_particles, RS::ParticlesMode p_mode) {
+ Particles *particles = particles_owner.getornull(p_particles);
+ ERR_FAIL_COND(!particles);
+ if (particles->mode == p_mode) {
+ return;
+ }
+
+ _particles_free_data(particles);
+
+ particles->mode = p_mode;
+}
+
void RendererStorageRD::particles_set_emitting(RID p_particles, bool p_emitting) {
Particles *particles = particles_owner.getornull(p_particles);
ERR_FAIL_COND(!particles);
@@ -4765,7 +4777,7 @@ void RendererStorageRD::particles_set_view_axis(RID p_particles, const Vector3 &
copy_push_constant.total_particles *= copy_push_constant.total_particles;
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
- RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, particles_shader.copy_pipelines[do_sort ? ParticlesShader::COPY_MODE_FILL_INSTANCES_WITH_SORT_BUFFER : ParticlesShader::COPY_MODE_FILL_INSTANCES]);
+ RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, particles_shader.copy_pipelines[do_sort ? ParticlesShader::COPY_MODE_FILL_INSTANCES_WITH_SORT_BUFFER : (particles->mode == RS::PARTICLES_MODE_2D ? ParticlesShader::COPY_MODE_FILL_INSTANCES_2D : ParticlesShader::COPY_MODE_FILL_INSTANCES)]);
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, particles->particles_copy_uniform_set, 0);
if (do_sort) {
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, particles->particles_sort_uniform_set, 1);
@@ -4785,8 +4797,12 @@ void RendererStorageRD::_particles_update_buffers(Particles *particles) {
if (particles->trails_enabled && particles->trail_bind_poses.size() > 1) {
total_amount *= particles->trail_bind_poses.size();
}
+
+ uint32_t xform_size = particles->mode == RS::PARTICLES_MODE_2D ? 2 : 3;
+
particles->particle_buffer = RD::get_singleton()->storage_buffer_create(sizeof(ParticleData) * total_amount);
- particles->particle_instance_buffer = RD::get_singleton()->storage_buffer_create(sizeof(float) * 4 * (3 + 1 + 1) * total_amount);
+
+ particles->particle_instance_buffer = RD::get_singleton()->storage_buffer_create(sizeof(float) * 4 * (xform_size + 1 + 1) * total_amount);
//needs to clear it
{
@@ -5004,7 +5020,7 @@ void RendererStorageRD::update_particles() {
}
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
- RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, particles_shader.copy_pipelines[ParticlesShader::COPY_MODE_FILL_INSTANCES]);
+ RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, particles_shader.copy_pipelines[particles->mode == RS::PARTICLES_MODE_2D ? ParticlesShader::COPY_MODE_FILL_INSTANCES_2D : ParticlesShader::COPY_MODE_FILL_INSTANCES]);
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, particles->particles_copy_uniform_set, 0);
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, particles->trail_bind_pose_uniform_set, 2);
RD::get_singleton()->compute_list_set_push_constant(compute_list, &copy_push_constant, sizeof(ParticlesShader::CopyPushConstant));
@@ -9220,6 +9236,7 @@ RendererStorageRD::RendererStorageRD() {
{
Vector<String> copy_modes;
copy_modes.push_back("\n#define MODE_FILL_INSTANCES\n");
+ copy_modes.push_back("\n#define MODE_FILL_INSTANCES\n#define MODE_2D\n");
copy_modes.push_back("\n#define MODE_FILL_SORT_BUFFER\n#define USE_SORT_BUFFER\n");
copy_modes.push_back("\n#define MODE_FILL_INSTANCES\n#define USE_SORT_BUFFER\n");
diff --git a/servers/rendering/renderer_rd/renderer_storage_rd.h b/servers/rendering/renderer_rd/renderer_storage_rd.h
index 961bdfb178..49f7f3dba6 100644
--- a/servers/rendering/renderer_rd/renderer_storage_rd.h
+++ b/servers/rendering/renderer_rd/renderer_storage_rd.h
@@ -580,6 +580,7 @@ private:
RID buffer; //storage buffer
RID uniform_set_3d;
+ RID uniform_set_2d;
bool dirty = false;
MultiMesh *dirty_list = nullptr;
@@ -696,6 +697,7 @@ private:
};
struct Particles {
+ RS::ParticlesMode mode = RS::PARTICLES_MODE_3D;
bool inactive = true;
float inactive_time = 0.0;
bool emitting = false;
@@ -822,6 +824,7 @@ private:
enum {
COPY_MODE_FILL_INSTANCES,
+ COPY_MODE_FILL_INSTANCES_2D,
COPY_MODE_FILL_SORT_BUFFER,
COPY_MODE_FILL_INSTANCES_WITH_SORT_BUFFER,
COPY_MODE_MAX,
@@ -1699,6 +1702,21 @@ public:
return multimesh->uniform_set_3d;
}
+ _FORCE_INLINE_ RID multimesh_get_2d_uniform_set(RID p_multimesh, RID p_shader, uint32_t p_set) const {
+ MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh);
+ if (!multimesh->uniform_set_2d.is_valid()) {
+ Vector<RD::Uniform> uniforms;
+ RD::Uniform u;
+ u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+ u.binding = 0;
+ u.ids.push_back(multimesh->buffer);
+ uniforms.push_back(u);
+ multimesh->uniform_set_2d = RD::get_singleton()->uniform_set_create(uniforms, p_shader, p_set);
+ }
+
+ return multimesh->uniform_set_2d;
+ }
+
/* IMMEDIATE API */
RID immediate_allocate() { return RID(); }
@@ -2093,6 +2111,7 @@ public:
RID particles_allocate();
void particles_initialize(RID p_particles_collision);
+ void particles_set_mode(RID p_particles, RS::ParticlesMode p_mode);
void particles_set_emitting(RID p_particles, bool p_emitting);
void particles_set_amount(RID p_particles, int p_amount);
void particles_set_lifetime(RID p_particles, float p_lifetime);
@@ -2137,6 +2156,12 @@ public:
virtual bool particles_is_inactive(RID p_particles) const;
+ _FORCE_INLINE_ RS::ParticlesMode particles_get_mode(RID p_particles) {
+ Particles *particles = particles_owner.getornull(p_particles);
+ ERR_FAIL_COND_V(!particles, RS::PARTICLES_MODE_2D);
+ return particles->mode;
+ }
+
_FORCE_INLINE_ uint32_t particles_get_amount(RID p_particles, uint32_t &r_trail_divisor) {
Particles *particles = particles_owner.getornull(p_particles);
ERR_FAIL_COND_V(!particles, 0);
diff --git a/servers/rendering/renderer_rd/shaders/canvas.glsl b/servers/rendering/renderer_rd/shaders/canvas.glsl
index 8b97ec119f..cf4c77db0d 100644
--- a/servers/rendering/renderer_rd/shaders/canvas.glsl
+++ b/servers/rendering/renderer_rd/shaders/canvas.glsl
@@ -84,40 +84,84 @@ void main() {
mat4 world_matrix = mat4(vec4(draw_data.world_x, 0.0, 0.0), vec4(draw_data.world_y, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(draw_data.world_ofs, 0.0, 1.0));
-#if 0
- if (draw_data.flags & FLAGS_INSTANCING_ENABLED) {
- uint offset = draw_data.flags & FLAGS_INSTANCING_STRIDE_MASK;
- offset *= gl_InstanceIndex;
- mat4 instance_xform = mat4(
- vec4(texelFetch(instancing_buffer, offset + 0), texelFetch(instancing_buffer, offset + 1), 0.0, texelFetch(instancing_buffer, offset + 3)),
- vec4(texelFetch(instancing_buffer, offset + 4), texelFetch(instancing_buffer, offset + 5), 0.0, texelFetch(instancing_buffer, offset + 7)),
- vec4(0.0, 0.0, 1.0, 0.0),
- vec4(0.0, 0.0, 0.0, 1.0));
- offset += 8;
- if (draw_data.flags & FLAGS_INSTANCING_HAS_COLORS) {
- vec4 instance_color;
- if (draw_data.flags & FLAGS_INSTANCING_COLOR_8_BIT) {
- uint bits = floatBitsToUint(texelFetch(instancing_buffer, offset));
- instance_color = unpackUnorm4x8(bits);
- offset += 1;
- } else {
- instance_color = vec4(texelFetch(instancing_buffer, offset + 0), texelFetch(instancing_buffer, offset + 1), texelFetch(instancing_buffer, offset + 2), texelFetch(instancing_buffer, offset + 3));
- offset += 4;
- }
+#define FLAGS_INSTANCING_MASK 0x7F
+#define FLAGS_INSTANCING_HAS_COLORS (1 << 7)
+#define FLAGS_INSTANCING_HAS_CUSTOM_DATA (1 << 8)
+
+ uint instancing = draw_data.flags & FLAGS_INSTANCING_MASK;
+
+#ifdef USE_ATTRIBUTES
+
+ if (instancing > 1) {
+ // trails
+
+ uint stride = 2 + 1 + 1; //particles always uses this format
- color *= instance_color;
+ uint trail_size = instancing;
+
+ uint offset = trail_size * stride * gl_InstanceIndex;
+
+ mat4 matrix;
+ vec4 pcolor;
+ {
+ uint boffset = offset + bone_attrib.x * stride;
+ matrix = mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0)) * weight_attrib.x;
+ pcolor = transforms.data[boffset + 3] * weight_attrib.x;
}
- if (draw_data.flags & FLAGS_INSTANCING_HAS_CUSTOM_DATA) {
- if (draw_data.flags & FLAGS_INSTANCING_CUSTOM_DATA_8_BIT) {
- uint bits = floatBitsToUint(texelFetch(instancing_buffer, offset));
- instance_custom = unpackUnorm4x8(bits);
- } else {
- instance_custom = vec4(texelFetch(instancing_buffer, offset + 0), texelFetch(instancing_buffer, offset + 1), texelFetch(instancing_buffer, offset + 2), texelFetch(instancing_buffer, offset + 3));
+ if (weight_attrib.y > 0.001) {
+ uint boffset = offset + bone_attrib.y * stride;
+ matrix += mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0)) * weight_attrib.y;
+ pcolor += transforms.data[boffset + 3] * weight_attrib.y;
+ }
+ if (weight_attrib.z > 0.001) {
+ uint boffset = offset + bone_attrib.z * stride;
+ matrix += mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0)) * weight_attrib.z;
+ pcolor += transforms.data[boffset + 3] * weight_attrib.z;
+ }
+ if (weight_attrib.w > 0.001) {
+ uint boffset = offset + bone_attrib.w * stride;
+ matrix += mat4(transforms.data[boffset + 0], transforms.data[boffset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0)) * weight_attrib.w;
+ pcolor += transforms.data[boffset + 3] * weight_attrib.w;
+ }
+
+ instance_custom = transforms.data[offset + 4];
+
+ color *= pcolor;
+
+ matrix = transpose(matrix);
+ world_matrix = world_matrix * matrix;
+
+ } else
+#endif // USE_ATTRIBUTES
+
+ if (instancing == 1) {
+ uint stride = 2;
+ {
+ if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_COLORS)) {
+ stride += 1;
+ }
+ if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_CUSTOM_DATA)) {
+ stride += 1;
}
}
- }
-#endif
+ uint offset = stride * gl_InstanceIndex;
+
+ mat4 matrix = mat4(transforms.data[offset + 0], transforms.data[offset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
+ offset += 2;
+
+ if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_COLORS)) {
+ color *= transforms.data[offset];
+ offset += 1;
+ }
+
+ if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_CUSTOM_DATA)) {
+ instance_custom = transforms.data[offset];
+ }
+
+ matrix = transpose(matrix);
+ world_matrix = world_matrix * matrix;
+ }
#if !defined(USE_ATTRIBUTES) && !defined(USE_PRIMITIVE)
if (bool(draw_data.flags & FLAGS_USING_PARTICLES)) {
diff --git a/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl b/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl
index cf7678ea31..451f9b0089 100644
--- a/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl
@@ -5,12 +5,10 @@
#define SDF_MAX_LENGTH 16384.0
-#define FLAGS_INSTANCING_STRIDE_MASK 0xF
-#define FLAGS_INSTANCING_ENABLED (1 << 4)
-#define FLAGS_INSTANCING_HAS_COLORS (1 << 5)
-#define FLAGS_INSTANCING_COLOR_8BIT (1 << 6)
-#define FLAGS_INSTANCING_HAS_CUSTOM_DATA (1 << 7)
-#define FLAGS_INSTANCING_CUSTOM_DATA_8_BIT (1 << 8)
+//1 means enabled, 2+ means trails in use
+#define FLAGS_INSTANCING_MASK 0x7F
+#define FLAGS_INSTANCING_HAS_COLORS (1 << 7)
+#define FLAGS_INSTANCING_HAS_CUSTOM_DATA (1 << 8)
#define FLAGS_CLIP_RECT_UV (1 << 9)
#define FLAGS_TRANSPOSE_RECT (1 << 10)
diff --git a/servers/rendering/renderer_rd/shaders/particles_copy.glsl b/servers/rendering/renderer_rd/shaders/particles_copy.glsl
index 7804d66d1c..e2bebadf1a 100644
--- a/servers/rendering/renderer_rd/shaders/particles_copy.glsl
+++ b/servers/rendering/renderer_rd/shaders/particles_copy.glsl
@@ -163,11 +163,20 @@ void main() {
txform = mat4(vec4(0.0), vec4(0.0), vec4(0.0), vec4(0.0)); //zero scale, becomes invisible
}
+#ifdef MODE_2D
+
+ instances.data[write_offset + 0] = txform[0];
+ instances.data[write_offset + 1] = txform[1];
+ instances.data[write_offset + 2] = particles.data[particle].color;
+ instances.data[write_offset + 3] = particles.data[particle].custom;
+
+#else
instances.data[write_offset + 0] = txform[0];
instances.data[write_offset + 1] = txform[1];
instances.data[write_offset + 2] = txform[2];
instances.data[write_offset + 3] = particles.data[particle].color;
instances.data[write_offset + 4] = particles.data[particle].custom;
+#endif //MODE_2D
#endif
}
diff --git a/servers/rendering/renderer_rd/shaders/skeleton.glsl b/servers/rendering/renderer_rd/shaders/skeleton.glsl
index 669ffc961d..b831005256 100644
--- a/servers/rendering/renderer_rd/shaders/skeleton.glsl
+++ b/servers/rendering/renderer_rd/shaders/skeleton.glsl
@@ -74,6 +74,53 @@ void main() {
#ifdef MODE_2D
vec2 vertex = uintBitsToFloat(uvec2(src_vertices.data[src_offset + 0], src_vertices.data[src_offset + 1]));
+
+ if (params.has_blend_shape) {
+ float blend_total = 0.0;
+ vec2 blend_vertex = vec2(0.0);
+
+ for (uint i = 0; i < params.blend_shape_count; i++) {
+ float w = blend_shape_weights.data[i];
+ if (abs(w) > 0.0001) {
+ uint base_offset = (params.vertex_count * i + index) * params.vertex_stride;
+
+ blend_vertex += uintBitsToFloat(uvec2(src_blend_shapes.data[base_offset + 0], src_blend_shapes.data[base_offset + 1])) * w;
+
+ base_offset += 2;
+
+ blend_total += w;
+ }
+ }
+
+ if (params.normalized_blend_shapes) {
+ vertex = (1.0 - blend_total) * vertex;
+ }
+
+ vertex += blend_vertex;
+ }
+
+ if (params.has_skeleton) {
+ uint skin_offset = params.skin_stride * index;
+
+ uvec2 bones = uvec2(src_bone_weights.data[skin_offset + 0], src_bone_weights.data[skin_offset + 1]);
+ uvec2 bones_01 = uvec2(bones.x & 0xFFFF, bones.x >> 16) * 3; //pre-add xform offset
+ uvec2 bones_23 = uvec2(bones.y & 0xFFFF, bones.y >> 16) * 3;
+
+ skin_offset += params.skin_weight_offset;
+
+ uvec2 weights = uvec2(src_bone_weights.data[skin_offset + 0], src_bone_weights.data[skin_offset + 1]);
+
+ vec2 weights_01 = unpackUnorm2x16(weights.x);
+ vec2 weights_23 = unpackUnorm2x16(weights.y);
+
+ mat4 m = mat4(bone_transforms.data[bones_01.x], bone_transforms.data[bones_01.x + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0)) * weights_01.x;
+ m += mat4(bone_transforms.data[bones_01.y], bone_transforms.data[bones_01.y + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0)) * weights_01.y;
+ m += mat4(bone_transforms.data[bones_23.x], bone_transforms.data[bones_23.x + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0)) * weights_23.x;
+ m += mat4(bone_transforms.data[bones_23.y], bone_transforms.data[bones_23.y + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0)) * weights_23.y;
+
+ //reverse order because its transposed
+ vertex = (vec4(vertex, 0.0, 1.0) * m).xy;
+ }
#else
vec3 vertex;
vec3 normal;
diff --git a/servers/rendering/renderer_storage.h b/servers/rendering/renderer_storage.h
index 15d99c038e..cc4ff5d55e 100644
--- a/servers/rendering/renderer_storage.h
+++ b/servers/rendering/renderer_storage.h
@@ -484,6 +484,7 @@ public:
virtual RID particles_allocate() = 0;
virtual void particles_initialize(RID p_rid) = 0;
+ virtual void particles_set_mode(RID p_particles, RS::ParticlesMode p_mode) = 0;
virtual void particles_set_emitting(RID p_particles, bool p_emitting) = 0;
virtual bool particles_get_emitting(RID p_particles) = 0;
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index c76ae1bb34..bf47d497b6 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -478,6 +478,7 @@ public:
FUNCRIDSPLIT(particles)
+ FUNC2(particles_set_mode, RID, ParticlesMode)
FUNC2(particles_set_emitting, RID, bool)
FUNC1R(bool, particles_get_emitting, RID)
FUNC2(particles_set_amount, RID, int)
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index a9154603ee..49990407e3 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -858,8 +858,10 @@ Error RenderingServer::mesh_create_surface_data_from_arrays(SurfaceData *r_surfa
case Variant::PACKED_VECTOR2_ARRAY: {
Vector<Vector2> v2 = p_arrays[i];
array_len = v2.size();
+ format |= ARRAY_FLAG_USE_2D_VERTICES;
} break;
case Variant::PACKED_VECTOR3_ARRAY: {
+ ERR_FAIL_COND_V(p_compress_format & ARRAY_FLAG_USE_2D_VERTICES, ERR_INVALID_PARAMETER);
Vector<Vector3> v3 = p_arrays[i];
array_len = v3.size();
} break;
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index ad965e9690..0c1a010f73 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -618,6 +618,12 @@ public:
virtual RID particles_create() = 0;
+ enum ParticlesMode {
+ PARTICLES_MODE_2D,
+ PARTICLES_MODE_3D
+ };
+ virtual void particles_set_mode(RID p_particles, ParticlesMode p_mode) = 0;
+
virtual void particles_set_emitting(RID p_particles, bool p_enable) = 0;
virtual bool particles_get_emitting(RID p_particles) = 0;
virtual void particles_set_amount(RID p_particles, int p_amount) = 0;