atlas_merging_dialog.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. /**************************************************************************/
  2. /* atlas_merging_dialog.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #include "atlas_merging_dialog.h"
  31. #include "editor/editor_undo_redo_manager.h"
  32. #include "editor/gui/editor_file_dialog.h"
  33. #include "editor/themes/editor_scale.h"
  34. #include "scene/gui/control.h"
  35. #include "scene/gui/split_container.h"
  36. #include "scene/resources/image_texture.h"
  37. void AtlasMergingDialog::_property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing) {
  38. _set(p_property, p_value);
  39. }
  40. void AtlasMergingDialog::_generate_merged(const Vector<Ref<TileSetAtlasSource>> &p_atlas_sources, int p_max_columns) {
  41. merged.instantiate();
  42. merged_mapping.clear();
  43. if (p_atlas_sources.size() >= 2) {
  44. Ref<Image> output_image = Image::create_empty(1, 1, false, Image::FORMAT_RGBA8);
  45. // Compute the new texture region size.
  46. Vector2i new_texture_region_size;
  47. for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) {
  48. const Ref<TileSetAtlasSource> &atlas_source = p_atlas_sources[source_index];
  49. new_texture_region_size = new_texture_region_size.max(atlas_source->get_texture_region_size());
  50. }
  51. // Generate the new texture.
  52. Vector2i atlas_offset;
  53. int line_height = 0;
  54. for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) {
  55. const Ref<TileSetAtlasSource> &atlas_source = p_atlas_sources[source_index];
  56. Ref<Image> input_image = atlas_source->get_texture()->get_image();
  57. if (input_image->get_format() != Image::FORMAT_RGBA8) {
  58. input_image->convert(Image::FORMAT_RGBA8);
  59. }
  60. merged_mapping.push_back(HashMap<Vector2i, Vector2i>());
  61. // Layout the tiles.
  62. Vector2i atlas_size;
  63. for (int tile_index = 0; tile_index < atlas_source->get_tiles_count(); tile_index++) {
  64. Vector2i tile_id = atlas_source->get_tile_id(tile_index);
  65. atlas_size = atlas_size.max(tile_id + atlas_source->get_tile_size_in_atlas(tile_id));
  66. Rect2i new_tile_rect_in_atlas = Rect2i(atlas_offset + tile_id, atlas_source->get_tile_size_in_atlas(tile_id));
  67. // Copy the texture.
  68. for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(tile_id); frame++) {
  69. Rect2i src_rect = atlas_source->get_tile_texture_region(tile_id, frame);
  70. Vector2i new_position = new_tile_rect_in_atlas.position * new_texture_region_size;
  71. if (frame > 0) {
  72. new_position += src_rect.size * Vector2i(frame, 0);
  73. atlas_size.x = MAX(frame + 1, atlas_size.x);
  74. }
  75. Rect2 dst_rect_wide = Rect2i(new_position, new_tile_rect_in_atlas.size * new_texture_region_size);
  76. if (dst_rect_wide.get_end().x > output_image->get_width() || dst_rect_wide.get_end().y > output_image->get_height()) {
  77. output_image->crop(MAX(dst_rect_wide.get_end().x, output_image->get_width()), MAX(dst_rect_wide.get_end().y, output_image->get_height()));
  78. }
  79. output_image->blit_rect(input_image, src_rect, dst_rect_wide.get_center() - src_rect.size / 2);
  80. }
  81. // Add to the mapping.
  82. merged_mapping[source_index][tile_id] = new_tile_rect_in_atlas.position;
  83. }
  84. // Compute the atlas offset.
  85. line_height = MAX(atlas_size.y, line_height);
  86. atlas_offset.x += atlas_size.x;
  87. if (atlas_offset.x >= p_max_columns) {
  88. atlas_offset.x = 0;
  89. atlas_offset.y += line_height;
  90. line_height = 0;
  91. }
  92. }
  93. merged->set_name(p_atlas_sources[0]->get_name());
  94. merged->set_texture(ImageTexture::create_from_image(output_image));
  95. merged->set_texture_region_size(new_texture_region_size);
  96. // Copy the tiles to the merged TileSetAtlasSource.
  97. for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) {
  98. const Ref<TileSetAtlasSource> &atlas_source = p_atlas_sources[source_index];
  99. for (KeyValue<Vector2i, Vector2i> tile_mapping : merged_mapping[source_index]) {
  100. // Create tiles and alternatives, then copy their properties.
  101. for (int alternative_index = 0; alternative_index < atlas_source->get_alternative_tiles_count(tile_mapping.key); alternative_index++) {
  102. int alternative_id = atlas_source->get_alternative_tile_id(tile_mapping.key, alternative_index);
  103. int changed_id = -1;
  104. if (alternative_id == 0) {
  105. merged->create_tile(tile_mapping.value, atlas_source->get_tile_size_in_atlas(tile_mapping.key));
  106. int count = atlas_source->get_tile_animation_frames_count(tile_mapping.key);
  107. merged->set_tile_animation_frames_count(tile_mapping.value, count);
  108. for (int i = 0; i < count; i++) {
  109. merged->set_tile_animation_frame_duration(tile_mapping.value, i, atlas_source->get_tile_animation_frame_duration(tile_mapping.key, i));
  110. }
  111. merged->set_tile_animation_speed(tile_mapping.value, atlas_source->get_tile_animation_speed(tile_mapping.key));
  112. merged->set_tile_animation_mode(tile_mapping.value, atlas_source->get_tile_animation_mode(tile_mapping.key));
  113. } else {
  114. changed_id = merged->create_alternative_tile(tile_mapping.value, alternative_index);
  115. }
  116. // Copy the properties.
  117. TileData *src_tile_data = atlas_source->get_tile_data(tile_mapping.key, alternative_id);
  118. List<PropertyInfo> properties;
  119. src_tile_data->get_property_list(&properties);
  120. TileData *dst_tile_data = merged->get_tile_data(tile_mapping.value, changed_id == -1 ? alternative_id : changed_id);
  121. for (PropertyInfo property : properties) {
  122. if (!(property.usage & PROPERTY_USAGE_STORAGE)) {
  123. continue;
  124. }
  125. Variant value = src_tile_data->get(property.name);
  126. Variant default_value = ClassDB::class_get_default_property_value("TileData", property.name);
  127. if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) {
  128. continue;
  129. }
  130. dst_tile_data->set(property.name, value);
  131. }
  132. }
  133. }
  134. }
  135. }
  136. }
  137. void AtlasMergingDialog::_update_texture() {
  138. Vector<int> selected = atlas_merging_atlases_list->get_selected_items();
  139. if (selected.size() >= 2) {
  140. Vector<Ref<TileSetAtlasSource>> to_merge;
  141. for (int i = 0; i < selected.size(); i++) {
  142. int source_id = atlas_merging_atlases_list->get_item_metadata(selected[i]);
  143. to_merge.push_back(tile_set->get_source(source_id));
  144. }
  145. _generate_merged(to_merge, next_line_after_column);
  146. preview->set_texture(merged->get_texture());
  147. preview->show();
  148. select_2_atlases_label->hide();
  149. get_ok_button()->set_disabled(false);
  150. merge_button->set_disabled(false);
  151. } else {
  152. _generate_merged(Vector<Ref<TileSetAtlasSource>>(), next_line_after_column);
  153. preview->set_texture(Ref<Texture2D>());
  154. preview->hide();
  155. select_2_atlases_label->show();
  156. get_ok_button()->set_disabled(true);
  157. merge_button->set_disabled(true);
  158. }
  159. }
  160. void AtlasMergingDialog::_merge_confirmed(const String &p_path) {
  161. ERR_FAIL_COND(merged.is_null());
  162. Ref<ImageTexture> output_image_texture = merged->get_texture();
  163. output_image_texture->get_image()->save_png(p_path);
  164. ResourceLoader::import(p_path);
  165. Ref<Texture2D> new_texture_resource = ResourceLoader::load(p_path, "Texture2D");
  166. merged->set_texture(new_texture_resource);
  167. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  168. undo_redo->create_action(TTR("Merge TileSetAtlasSource"));
  169. int next_id = tile_set->get_next_source_id();
  170. undo_redo->add_do_method(*tile_set, "add_source", merged, next_id);
  171. undo_redo->add_undo_method(*tile_set, "remove_source", next_id);
  172. if (delete_original_atlases) {
  173. // Delete originals if needed.
  174. Vector<int> selected = atlas_merging_atlases_list->get_selected_items();
  175. for (int i = 0; i < selected.size(); i++) {
  176. int source_id = atlas_merging_atlases_list->get_item_metadata(selected[i]);
  177. Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id);
  178. undo_redo->add_do_method(*tile_set, "remove_source", source_id);
  179. undo_redo->add_undo_method(*tile_set, "add_source", tas, source_id);
  180. // Add the tile proxies.
  181. for (int tile_index = 0; tile_index < tas->get_tiles_count(); tile_index++) {
  182. Vector2i tile_id = tas->get_tile_id(tile_index);
  183. undo_redo->add_do_method(*tile_set, "set_coords_level_tile_proxy", source_id, tile_id, next_id, merged_mapping[i][tile_id]);
  184. if (tile_set->has_coords_level_tile_proxy(source_id, tile_id)) {
  185. Array a = tile_set->get_coords_level_tile_proxy(source_id, tile_id);
  186. undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", a[0], a[1]);
  187. } else {
  188. undo_redo->add_undo_method(*tile_set, "remove_coords_level_tile_proxy", source_id, tile_id);
  189. }
  190. }
  191. }
  192. }
  193. undo_redo->commit_action();
  194. committed_actions_count++;
  195. hide();
  196. }
  197. void AtlasMergingDialog::ok_pressed() {
  198. delete_original_atlases = false;
  199. editor_file_dialog->popup_file_dialog();
  200. }
  201. void AtlasMergingDialog::cancel_pressed() {
  202. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  203. for (int i = 0; i < committed_actions_count; i++) {
  204. undo_redo->undo();
  205. }
  206. committed_actions_count = 0;
  207. }
  208. void AtlasMergingDialog::custom_action(const String &p_action) {
  209. if (p_action == "merge") {
  210. delete_original_atlases = true;
  211. editor_file_dialog->popup_file_dialog();
  212. }
  213. }
  214. bool AtlasMergingDialog::_set(const StringName &p_name, const Variant &p_value) {
  215. if (p_name == "next_line_after_column" && p_value.get_type() == Variant::INT) {
  216. next_line_after_column = p_value;
  217. _update_texture();
  218. return true;
  219. }
  220. return false;
  221. }
  222. bool AtlasMergingDialog::_get(const StringName &p_name, Variant &r_ret) const {
  223. if (p_name == "next_line_after_column") {
  224. r_ret = next_line_after_column;
  225. return true;
  226. }
  227. return false;
  228. }
  229. void AtlasMergingDialog::_notification(int p_what) {
  230. switch (p_what) {
  231. case NOTIFICATION_VISIBILITY_CHANGED: {
  232. if (is_visible()) {
  233. _update_texture();
  234. }
  235. } break;
  236. }
  237. }
  238. void AtlasMergingDialog::update_tile_set(Ref<TileSet> p_tile_set) {
  239. ERR_FAIL_COND(p_tile_set.is_null());
  240. tile_set = p_tile_set;
  241. atlas_merging_atlases_list->clear();
  242. for (int i = 0; i < p_tile_set->get_source_count(); i++) {
  243. int source_id = p_tile_set->get_source_id(i);
  244. Ref<TileSetAtlasSource> atlas_source = p_tile_set->get_source(source_id);
  245. if (atlas_source.is_valid()) {
  246. Ref<Texture2D> texture = atlas_source->get_texture();
  247. if (texture.is_valid()) {
  248. String item_text = vformat(TTR("%s (ID: %d)"), texture->get_path().get_file(), source_id);
  249. atlas_merging_atlases_list->add_item(item_text, texture);
  250. atlas_merging_atlases_list->set_item_metadata(-1, source_id);
  251. }
  252. }
  253. }
  254. get_ok_button()->set_disabled(true);
  255. merge_button->set_disabled(true);
  256. committed_actions_count = 0;
  257. }
  258. AtlasMergingDialog::AtlasMergingDialog() {
  259. // Atlas merging window.
  260. set_title(TTR("Atlas Merging"));
  261. set_hide_on_ok(false);
  262. // Ok buttons
  263. set_ok_button_text(TTR("Merge (Keep original Atlases)"));
  264. get_ok_button()->set_disabled(true);
  265. merge_button = add_button(TTR("Merge"), true, "merge");
  266. merge_button->set_disabled(true);
  267. HSplitContainer *atlas_merging_h_split_container = memnew(HSplitContainer);
  268. atlas_merging_h_split_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  269. atlas_merging_h_split_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
  270. add_child(atlas_merging_h_split_container);
  271. // Atlas sources item list.
  272. atlas_merging_atlases_list = memnew(ItemList);
  273. atlas_merging_atlases_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
  274. atlas_merging_atlases_list->set_fixed_icon_size(Size2(60, 60) * EDSCALE);
  275. atlas_merging_atlases_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  276. atlas_merging_atlases_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
  277. atlas_merging_atlases_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
  278. atlas_merging_atlases_list->set_custom_minimum_size(Size2(100, 200));
  279. atlas_merging_atlases_list->set_select_mode(ItemList::SELECT_MULTI);
  280. atlas_merging_atlases_list->set_theme_type_variation("ItemListSecondary");
  281. atlas_merging_atlases_list->connect("multi_selected", callable_mp(this, &AtlasMergingDialog::_update_texture).unbind(2));
  282. atlas_merging_h_split_container->add_child(atlas_merging_atlases_list);
  283. VBoxContainer *atlas_merging_right_panel = memnew(VBoxContainer);
  284. atlas_merging_right_panel->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  285. atlas_merging_right_panel->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
  286. atlas_merging_h_split_container->add_child(atlas_merging_right_panel);
  287. // Settings.
  288. Label *settings_label = memnew(Label);
  289. settings_label->set_text(TTR("Settings:"));
  290. atlas_merging_right_panel->add_child(settings_label);
  291. columns_editor_property = memnew(EditorPropertyInteger);
  292. columns_editor_property->set_label(TTR("Next Line After Column"));
  293. columns_editor_property->set_object_and_property(this, "next_line_after_column");
  294. columns_editor_property->update_property();
  295. columns_editor_property->connect("property_changed", callable_mp(this, &AtlasMergingDialog::_property_changed));
  296. atlas_merging_right_panel->add_child(columns_editor_property);
  297. // Preview.
  298. Label *preview_label = memnew(Label);
  299. preview_label->set_text(TTR("Preview:"));
  300. atlas_merging_right_panel->add_child(preview_label);
  301. preview = memnew(TextureRect);
  302. preview->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  303. preview->set_v_size_flags(Control::SIZE_EXPAND_FILL);
  304. preview->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
  305. preview->hide();
  306. preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
  307. atlas_merging_right_panel->add_child(preview);
  308. select_2_atlases_label = memnew(Label);
  309. select_2_atlases_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  310. select_2_atlases_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
  311. select_2_atlases_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
  312. select_2_atlases_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
  313. select_2_atlases_label->set_text(TTR("Please select two atlases or more."));
  314. atlas_merging_right_panel->add_child(select_2_atlases_label);
  315. // The file dialog to choose the texture path.
  316. editor_file_dialog = memnew(EditorFileDialog);
  317. editor_file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
  318. editor_file_dialog->add_filter("*.png");
  319. editor_file_dialog->connect("file_selected", callable_mp(this, &AtlasMergingDialog::_merge_confirmed));
  320. add_child(editor_file_dialog);
  321. }