Path: blob/master/editor/scene/2d/tiles/atlas_merging_dialog.cpp
21733 views
/**************************************************************************/1/* atlas_merging_dialog.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "atlas_merging_dialog.h"3132#include "editor/editor_undo_redo_manager.h"33#include "editor/gui/editor_file_dialog.h"34#include "editor/themes/editor_scale.h"35#include "scene/gui/control.h"36#include "scene/gui/split_container.h"37#include "scene/resources/image_texture.h"3839void AtlasMergingDialog::_property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing) {40_set(p_property, p_value);41}4243void AtlasMergingDialog::_generate_merged(const Vector<Ref<TileSetAtlasSource>> &p_atlas_sources, int p_max_columns) {44merged.instantiate();45merged_mapping.clear();4647if (p_atlas_sources.size() >= 2) {48Ref<Image> output_image = Image::create_empty(1, 1, false, Image::FORMAT_RGBA8);4950// Compute the new texture region size.51Vector2i new_texture_region_size;52for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) {53const Ref<TileSetAtlasSource> &atlas_source = p_atlas_sources[source_index];54new_texture_region_size = new_texture_region_size.max(atlas_source->get_texture_region_size());55}5657// Generate the new texture.58Vector2i atlas_offset;59int line_height = 0;60for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) {61const Ref<TileSetAtlasSource> &atlas_source = p_atlas_sources[source_index];62Ref<Image> input_image = atlas_source->get_texture()->get_image();63if (input_image->get_format() != Image::FORMAT_RGBA8) {64input_image->convert(Image::FORMAT_RGBA8);65}66merged_mapping.push_back(HashMap<Vector2i, Vector2i>());6768// Layout the tiles.69Vector2i atlas_size;7071for (int tile_index = 0; tile_index < atlas_source->get_tiles_count(); tile_index++) {72Vector2i tile_id = atlas_source->get_tile_id(tile_index);73atlas_size = atlas_size.max(tile_id + atlas_source->get_tile_size_in_atlas(tile_id));7475Rect2i new_tile_rect_in_atlas = Rect2i(atlas_offset + tile_id, atlas_source->get_tile_size_in_atlas(tile_id));7677int columns = atlas_source->get_tile_animation_columns(tile_id);78Vector2i anim_separation = atlas_source->get_tile_animation_separation(tile_id);79Vector2i size_in_atlas = atlas_source->get_tile_size_in_atlas(tile_id);8081// Copy the texture.82for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(tile_id); frame++) {83Rect2i src_rect = atlas_source->get_tile_texture_region(tile_id, frame);84Vector2i new_position = new_tile_rect_in_atlas.position * new_texture_region_size;8586if (frame > 0) {87Vector2i frame_coords;88if (columns > 0) {89// Clamp x frame coordinate by number of max columns( `frame` % `columns`).90// Set y frame coordinate to the whole number of times a complete91// row of columns can be made( `frame` / `column` ).92// These two steps combined convert a 1D index(`frame`) into a93// 2D coordinate(`frame_coords`).94frame_coords = new_tile_rect_in_atlas.position + (size_in_atlas + anim_separation) * Vector2i(frame % columns, frame / columns);95} else {96// Godot lays frames out horizontally(`Vector2i(frame,0)`) if columns are set to 0.97frame_coords = new_tile_rect_in_atlas.position + (size_in_atlas + anim_separation) * Vector2i(frame, 0);98}99// Enlarge the atlas offset if new frame_coords fall outside its current dimensions.100atlas_size.x = MAX(frame_coords.x + 1, atlas_size.x);101atlas_size.y = MAX(frame_coords.y + 1, atlas_size.y);102103new_position = frame_coords * new_texture_region_size;104}105Rect2 dst_rect_wide = Rect2i(new_position, new_tile_rect_in_atlas.size * new_texture_region_size);106// Enlarge image if the destination boundary falls outside its current dimensions.107if (dst_rect_wide.get_end().x > output_image->get_width() || dst_rect_wide.get_end().y > output_image->get_height()) {108output_image->crop(MAX(dst_rect_wide.get_end().x, output_image->get_width()), MAX(dst_rect_wide.get_end().y, output_image->get_height()));109}110output_image->blit_rect(input_image, src_rect, dst_rect_wide.get_center() - src_rect.size / 2);111}112113// Add to the mapping.114merged_mapping[source_index][tile_id] = new_tile_rect_in_atlas.position;115}116117// Compute the atlas offset.118line_height = MAX(atlas_size.y, line_height);119atlas_offset.x += atlas_size.x;120if (atlas_offset.x >= p_max_columns) {121atlas_offset.x = 0;122atlas_offset.y += line_height;123line_height = 0;124}125}126127merged->set_name(p_atlas_sources[0]->get_name());128merged->set_texture(ImageTexture::create_from_image(output_image));129merged->set_texture_region_size(new_texture_region_size);130131// Copy the tiles to the merged TileSetAtlasSource.132for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) {133const Ref<TileSetAtlasSource> &atlas_source = p_atlas_sources[source_index];134for (KeyValue<Vector2i, Vector2i> tile_mapping : merged_mapping[source_index]) {135// Create tiles and alternatives, then copy their properties.136for (int alternative_index = 0; alternative_index < atlas_source->get_alternative_tiles_count(tile_mapping.key); alternative_index++) {137int alternative_id = atlas_source->get_alternative_tile_id(tile_mapping.key, alternative_index);138int changed_id = -1;139if (alternative_id == 0) {140merged->create_tile(tile_mapping.value, atlas_source->get_tile_size_in_atlas(tile_mapping.key));141142// Copies are done in order as seen inside the editor143// - for cross-referencing and organizational purposes.144145// Copy over `columns`.146int columns = atlas_source->get_tile_animation_columns(tile_mapping.key);147merged->set_tile_animation_columns(tile_mapping.value, columns);148149// Copy over `separation`.150Vector2i separation = atlas_source->get_tile_animation_separation(tile_mapping.key);151merged->set_tile_animation_separation(tile_mapping.value, separation);152153// Copy over speed.154merged->set_tile_animation_speed(tile_mapping.value, atlas_source->get_tile_animation_speed(tile_mapping.key));155156// Copy over mode.157merged->set_tile_animation_mode(tile_mapping.value, atlas_source->get_tile_animation_mode(tile_mapping.key));158159// Copy over `count` and frame durations.160int count = atlas_source->get_tile_animation_frames_count(tile_mapping.key);161merged->set_tile_animation_frames_count(tile_mapping.value, count);162for (int i = 0; i < count; i++) {163merged->set_tile_animation_frame_duration(tile_mapping.value, i, atlas_source->get_tile_animation_frame_duration(tile_mapping.key, i));164}165merged->set_tile_animation_speed(tile_mapping.value, atlas_source->get_tile_animation_speed(tile_mapping.key));166} else {167changed_id = merged->create_alternative_tile(tile_mapping.value, alternative_index);168}169170// Copy the properties.171TileData *src_tile_data = atlas_source->get_tile_data(tile_mapping.key, alternative_id);172List<PropertyInfo> properties;173src_tile_data->get_property_list(&properties);174175TileData *dst_tile_data = merged->get_tile_data(tile_mapping.value, changed_id == -1 ? alternative_id : changed_id);176for (PropertyInfo property : properties) {177// Only copy over properties that are used for storage.178if (!(property.usage & PROPERTY_USAGE_STORAGE)) {179continue;180}181182// Only copy over properties that are not default.183Variant value = src_tile_data->get(property.name);184Variant default_value = ClassDB::class_get_default_property_value("TileData", property.name);185if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) {186continue;187}188189// DO NOT try to copy "script" properties that are null190// as this causes a crash - see issue #101132.191// I believe this *should* be covered by the default192// check that is done above but the call to193// Variant::evaluate(Variant::OP_EQUAL, <null>, <null>) returns false.194// I do not know if this is intended behavior or a bug.195if (property.name == "script" && value.is_null()) {196continue;197}198dst_tile_data->set(property.name, value);199}200}201}202}203}204}205206void AtlasMergingDialog::_update_texture() {207Vector<int> selected = atlas_merging_atlases_list->get_selected_items();208if (selected.size() >= 2) {209Vector<Ref<TileSetAtlasSource>> to_merge;210for (int i = 0; i < selected.size(); i++) {211int source_id = atlas_merging_atlases_list->get_item_metadata(selected[i]);212to_merge.push_back(tile_set->get_source(source_id));213}214_generate_merged(to_merge, next_line_after_column);215preview->set_texture(merged->get_texture());216preview->show();217select_2_atlases_label->hide();218get_ok_button()->set_disabled(false);219merge_button->set_disabled(false);220} else {221_generate_merged(Vector<Ref<TileSetAtlasSource>>(), next_line_after_column);222preview->set_texture(Ref<Texture2D>());223preview->hide();224select_2_atlases_label->show();225get_ok_button()->set_disabled(true);226merge_button->set_disabled(true);227}228}229230void AtlasMergingDialog::_merge_confirmed(const String &p_path) {231ERR_FAIL_COND(merged.is_null());232233Ref<ImageTexture> output_image_texture = merged->get_texture();234output_image_texture->get_image()->save_png(p_path);235236ResourceLoader::import(p_path);237238Ref<Texture2D> new_texture_resource = ResourceLoader::load(p_path, "Texture2D");239merged->set_texture(new_texture_resource);240241EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();242undo_redo->create_action(TTR("Merge TileSetAtlasSource"));243int next_id = tile_set->get_next_source_id();244undo_redo->add_do_method(*tile_set, "add_source", merged, next_id);245undo_redo->add_undo_method(*tile_set, "remove_source", next_id);246247if (delete_original_atlases) {248// Delete originals if needed.249Vector<int> selected = atlas_merging_atlases_list->get_selected_items();250for (int i = 0; i < selected.size(); i++) {251int source_id = atlas_merging_atlases_list->get_item_metadata(selected[i]);252Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id);253undo_redo->add_do_method(*tile_set, "remove_source", source_id);254undo_redo->add_undo_method(*tile_set, "add_source", tas, source_id);255256// Add the tile proxies.257for (int tile_index = 0; tile_index < tas->get_tiles_count(); tile_index++) {258Vector2i tile_id = tas->get_tile_id(tile_index);259undo_redo->add_do_method(*tile_set, "set_coords_level_tile_proxy", source_id, tile_id, next_id, merged_mapping[i][tile_id]);260if (tile_set->has_coords_level_tile_proxy(source_id, tile_id)) {261Array a = tile_set->get_coords_level_tile_proxy(source_id, tile_id);262undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", a[0], a[1]);263} else {264undo_redo->add_undo_method(*tile_set, "remove_coords_level_tile_proxy", source_id, tile_id);265}266}267}268}269undo_redo->commit_action();270committed_actions_count++;271272hide();273}274275void AtlasMergingDialog::ok_pressed() {276delete_original_atlases = false;277editor_file_dialog->popup_file_dialog();278}279280void AtlasMergingDialog::cancel_pressed() {281EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();282for (int i = 0; i < committed_actions_count; i++) {283undo_redo->undo();284}285committed_actions_count = 0;286}287288void AtlasMergingDialog::custom_action(const String &p_action) {289if (p_action == "merge") {290delete_original_atlases = true;291editor_file_dialog->popup_file_dialog();292}293}294295bool AtlasMergingDialog::_set(const StringName &p_name, const Variant &p_value) {296if (p_name == "next_line_after_column" && p_value.get_type() == Variant::INT) {297next_line_after_column = p_value;298_update_texture();299return true;300}301return false;302}303304bool AtlasMergingDialog::_get(const StringName &p_name, Variant &r_ret) const {305if (p_name == "next_line_after_column") {306r_ret = next_line_after_column;307return true;308}309return false;310}311312void AtlasMergingDialog::_notification(int p_what) {313switch (p_what) {314case NOTIFICATION_VISIBILITY_CHANGED: {315if (is_visible()) {316_update_texture();317}318} break;319}320}321322void AtlasMergingDialog::update_tile_set(Ref<TileSet> p_tile_set) {323ERR_FAIL_COND(p_tile_set.is_null());324tile_set = p_tile_set;325326atlas_merging_atlases_list->clear();327for (int i = 0; i < p_tile_set->get_source_count(); i++) {328int source_id = p_tile_set->get_source_id(i);329Ref<TileSetAtlasSource> atlas_source = p_tile_set->get_source(source_id);330if (atlas_source.is_valid()) {331Ref<Texture2D> texture = atlas_source->get_texture();332if (texture.is_valid()) {333String item_text = vformat(TTR("%s (ID: %d)"), texture->get_path().get_file(), source_id);334atlas_merging_atlases_list->add_item(item_text, texture);335atlas_merging_atlases_list->set_item_metadata(-1, source_id);336}337}338}339340get_ok_button()->set_disabled(true);341merge_button->set_disabled(true);342343committed_actions_count = 0;344}345346AtlasMergingDialog::AtlasMergingDialog() {347// Atlas merging window.348set_title(TTR("Atlas Merging"));349set_hide_on_ok(false);350351// Ok buttons352set_ok_button_text(TTR("Merge (Keep original Atlases)"));353get_ok_button()->set_disabled(true);354merge_button = add_button(TTR("Merge"), true, "merge");355merge_button->set_disabled(true);356357HSplitContainer *atlas_merging_h_split_container = memnew(HSplitContainer);358atlas_merging_h_split_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);359atlas_merging_h_split_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);360add_child(atlas_merging_h_split_container);361362// Atlas sources item list.363atlas_merging_atlases_list = memnew(ItemList);364atlas_merging_atlases_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);365atlas_merging_atlases_list->set_fixed_icon_size(Size2(60, 60) * EDSCALE);366atlas_merging_atlases_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);367atlas_merging_atlases_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);368atlas_merging_atlases_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);369atlas_merging_atlases_list->set_custom_minimum_size(Size2(100, 200));370atlas_merging_atlases_list->set_select_mode(ItemList::SELECT_MULTI);371atlas_merging_atlases_list->set_theme_type_variation("ItemListSecondary");372atlas_merging_atlases_list->connect("multi_selected", callable_mp(this, &AtlasMergingDialog::_update_texture).unbind(2));373atlas_merging_h_split_container->add_child(atlas_merging_atlases_list);374375VBoxContainer *atlas_merging_right_panel = memnew(VBoxContainer);376atlas_merging_right_panel->set_h_size_flags(Control::SIZE_EXPAND_FILL);377atlas_merging_right_panel->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);378atlas_merging_h_split_container->add_child(atlas_merging_right_panel);379380// Settings.381Label *settings_label = memnew(Label);382settings_label->set_text(TTR("Settings:"));383atlas_merging_right_panel->add_child(settings_label);384385columns_editor_property = memnew(EditorPropertyInteger);386columns_editor_property->set_label(TTR("Next Line After Column"));387columns_editor_property->set_object_and_property(this, "next_line_after_column");388columns_editor_property->update_property();389columns_editor_property->connect("property_changed", callable_mp(this, &AtlasMergingDialog::_property_changed));390atlas_merging_right_panel->add_child(columns_editor_property);391392// Preview.393Label *preview_label = memnew(Label);394preview_label->set_text(TTR("Preview:"));395atlas_merging_right_panel->add_child(preview_label);396397preview = memnew(TextureRect);398preview->set_h_size_flags(Control::SIZE_EXPAND_FILL);399preview->set_v_size_flags(Control::SIZE_EXPAND_FILL);400preview->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);401preview->hide();402preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);403atlas_merging_right_panel->add_child(preview);404405select_2_atlases_label = memnew(Label);406select_2_atlases_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY);407select_2_atlases_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);408select_2_atlases_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);409select_2_atlases_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);410select_2_atlases_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);411select_2_atlases_label->set_text(TTR("Please select two atlases or more."));412atlas_merging_right_panel->add_child(select_2_atlases_label);413414// The file dialog to choose the texture path.415editor_file_dialog = memnew(EditorFileDialog);416editor_file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);417editor_file_dialog->add_filter("*.png");418editor_file_dialog->connect("file_selected", callable_mp(this, &AtlasMergingDialog::_merge_confirmed));419add_child(editor_file_dialog);420}421422423