Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/scene/2d/tiles/atlas_merging_dialog.cpp
9912 views
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
31
#include "atlas_merging_dialog.h"
32
33
#include "editor/editor_undo_redo_manager.h"
34
#include "editor/gui/editor_file_dialog.h"
35
#include "editor/themes/editor_scale.h"
36
#include "scene/gui/control.h"
37
#include "scene/gui/split_container.h"
38
#include "scene/resources/image_texture.h"
39
40
void AtlasMergingDialog::_property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing) {
41
_set(p_property, p_value);
42
}
43
44
void AtlasMergingDialog::_generate_merged(const Vector<Ref<TileSetAtlasSource>> &p_atlas_sources, int p_max_columns) {
45
merged.instantiate();
46
merged_mapping.clear();
47
48
if (p_atlas_sources.size() >= 2) {
49
Ref<Image> output_image = Image::create_empty(1, 1, false, Image::FORMAT_RGBA8);
50
51
// Compute the new texture region size.
52
Vector2i new_texture_region_size;
53
for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) {
54
const Ref<TileSetAtlasSource> &atlas_source = p_atlas_sources[source_index];
55
new_texture_region_size = new_texture_region_size.max(atlas_source->get_texture_region_size());
56
}
57
58
// Generate the new texture.
59
Vector2i atlas_offset;
60
int line_height = 0;
61
for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) {
62
const Ref<TileSetAtlasSource> &atlas_source = p_atlas_sources[source_index];
63
Ref<Image> input_image = atlas_source->get_texture()->get_image();
64
if (input_image->get_format() != Image::FORMAT_RGBA8) {
65
input_image->convert(Image::FORMAT_RGBA8);
66
}
67
merged_mapping.push_back(HashMap<Vector2i, Vector2i>());
68
69
// Layout the tiles.
70
Vector2i atlas_size;
71
72
for (int tile_index = 0; tile_index < atlas_source->get_tiles_count(); tile_index++) {
73
Vector2i tile_id = atlas_source->get_tile_id(tile_index);
74
atlas_size = atlas_size.max(tile_id + atlas_source->get_tile_size_in_atlas(tile_id));
75
76
Rect2i new_tile_rect_in_atlas = Rect2i(atlas_offset + tile_id, atlas_source->get_tile_size_in_atlas(tile_id));
77
78
// Copy the texture.
79
for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(tile_id); frame++) {
80
Rect2i src_rect = atlas_source->get_tile_texture_region(tile_id, frame);
81
Vector2i new_position = new_tile_rect_in_atlas.position * new_texture_region_size;
82
if (frame > 0) {
83
new_position += src_rect.size * Vector2i(frame, 0);
84
atlas_size.x = MAX(frame + 1, atlas_size.x);
85
}
86
Rect2 dst_rect_wide = Rect2i(new_position, new_tile_rect_in_atlas.size * new_texture_region_size);
87
if (dst_rect_wide.get_end().x > output_image->get_width() || dst_rect_wide.get_end().y > output_image->get_height()) {
88
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()));
89
}
90
output_image->blit_rect(input_image, src_rect, dst_rect_wide.get_center() - src_rect.size / 2);
91
}
92
93
// Add to the mapping.
94
merged_mapping[source_index][tile_id] = new_tile_rect_in_atlas.position;
95
}
96
97
// Compute the atlas offset.
98
line_height = MAX(atlas_size.y, line_height);
99
atlas_offset.x += atlas_size.x;
100
if (atlas_offset.x >= p_max_columns) {
101
atlas_offset.x = 0;
102
atlas_offset.y += line_height;
103
line_height = 0;
104
}
105
}
106
107
merged->set_name(p_atlas_sources[0]->get_name());
108
merged->set_texture(ImageTexture::create_from_image(output_image));
109
merged->set_texture_region_size(new_texture_region_size);
110
111
// Copy the tiles to the merged TileSetAtlasSource.
112
for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) {
113
const Ref<TileSetAtlasSource> &atlas_source = p_atlas_sources[source_index];
114
for (KeyValue<Vector2i, Vector2i> tile_mapping : merged_mapping[source_index]) {
115
// Create tiles and alternatives, then copy their properties.
116
for (int alternative_index = 0; alternative_index < atlas_source->get_alternative_tiles_count(tile_mapping.key); alternative_index++) {
117
int alternative_id = atlas_source->get_alternative_tile_id(tile_mapping.key, alternative_index);
118
int changed_id = -1;
119
if (alternative_id == 0) {
120
merged->create_tile(tile_mapping.value, atlas_source->get_tile_size_in_atlas(tile_mapping.key));
121
int count = atlas_source->get_tile_animation_frames_count(tile_mapping.key);
122
merged->set_tile_animation_frames_count(tile_mapping.value, count);
123
for (int i = 0; i < count; i++) {
124
merged->set_tile_animation_frame_duration(tile_mapping.value, i, atlas_source->get_tile_animation_frame_duration(tile_mapping.key, i));
125
}
126
merged->set_tile_animation_speed(tile_mapping.value, atlas_source->get_tile_animation_speed(tile_mapping.key));
127
merged->set_tile_animation_mode(tile_mapping.value, atlas_source->get_tile_animation_mode(tile_mapping.key));
128
} else {
129
changed_id = merged->create_alternative_tile(tile_mapping.value, alternative_index);
130
}
131
132
// Copy the properties.
133
TileData *src_tile_data = atlas_source->get_tile_data(tile_mapping.key, alternative_id);
134
List<PropertyInfo> properties;
135
src_tile_data->get_property_list(&properties);
136
137
TileData *dst_tile_data = merged->get_tile_data(tile_mapping.value, changed_id == -1 ? alternative_id : changed_id);
138
for (PropertyInfo property : properties) {
139
if (!(property.usage & PROPERTY_USAGE_STORAGE)) {
140
continue;
141
}
142
Variant value = src_tile_data->get(property.name);
143
Variant default_value = ClassDB::class_get_default_property_value("TileData", property.name);
144
if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) {
145
continue;
146
}
147
dst_tile_data->set(property.name, value);
148
}
149
}
150
}
151
}
152
}
153
}
154
155
void AtlasMergingDialog::_update_texture() {
156
Vector<int> selected = atlas_merging_atlases_list->get_selected_items();
157
if (selected.size() >= 2) {
158
Vector<Ref<TileSetAtlasSource>> to_merge;
159
for (int i = 0; i < selected.size(); i++) {
160
int source_id = atlas_merging_atlases_list->get_item_metadata(selected[i]);
161
to_merge.push_back(tile_set->get_source(source_id));
162
}
163
_generate_merged(to_merge, next_line_after_column);
164
preview->set_texture(merged->get_texture());
165
preview->show();
166
select_2_atlases_label->hide();
167
get_ok_button()->set_disabled(false);
168
merge_button->set_disabled(false);
169
} else {
170
_generate_merged(Vector<Ref<TileSetAtlasSource>>(), next_line_after_column);
171
preview->set_texture(Ref<Texture2D>());
172
preview->hide();
173
select_2_atlases_label->show();
174
get_ok_button()->set_disabled(true);
175
merge_button->set_disabled(true);
176
}
177
}
178
179
void AtlasMergingDialog::_merge_confirmed(const String &p_path) {
180
ERR_FAIL_COND(merged.is_null());
181
182
Ref<ImageTexture> output_image_texture = merged->get_texture();
183
output_image_texture->get_image()->save_png(p_path);
184
185
ResourceLoader::import(p_path);
186
187
Ref<Texture2D> new_texture_resource = ResourceLoader::load(p_path, "Texture2D");
188
merged->set_texture(new_texture_resource);
189
190
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
191
undo_redo->create_action(TTR("Merge TileSetAtlasSource"));
192
int next_id = tile_set->get_next_source_id();
193
undo_redo->add_do_method(*tile_set, "add_source", merged, next_id);
194
undo_redo->add_undo_method(*tile_set, "remove_source", next_id);
195
196
if (delete_original_atlases) {
197
// Delete originals if needed.
198
Vector<int> selected = atlas_merging_atlases_list->get_selected_items();
199
for (int i = 0; i < selected.size(); i++) {
200
int source_id = atlas_merging_atlases_list->get_item_metadata(selected[i]);
201
Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id);
202
undo_redo->add_do_method(*tile_set, "remove_source", source_id);
203
undo_redo->add_undo_method(*tile_set, "add_source", tas, source_id);
204
205
// Add the tile proxies.
206
for (int tile_index = 0; tile_index < tas->get_tiles_count(); tile_index++) {
207
Vector2i tile_id = tas->get_tile_id(tile_index);
208
undo_redo->add_do_method(*tile_set, "set_coords_level_tile_proxy", source_id, tile_id, next_id, merged_mapping[i][tile_id]);
209
if (tile_set->has_coords_level_tile_proxy(source_id, tile_id)) {
210
Array a = tile_set->get_coords_level_tile_proxy(source_id, tile_id);
211
undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", a[0], a[1]);
212
} else {
213
undo_redo->add_undo_method(*tile_set, "remove_coords_level_tile_proxy", source_id, tile_id);
214
}
215
}
216
}
217
}
218
undo_redo->commit_action();
219
committed_actions_count++;
220
221
hide();
222
}
223
224
void AtlasMergingDialog::ok_pressed() {
225
delete_original_atlases = false;
226
editor_file_dialog->popup_file_dialog();
227
}
228
229
void AtlasMergingDialog::cancel_pressed() {
230
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
231
for (int i = 0; i < committed_actions_count; i++) {
232
undo_redo->undo();
233
}
234
committed_actions_count = 0;
235
}
236
237
void AtlasMergingDialog::custom_action(const String &p_action) {
238
if (p_action == "merge") {
239
delete_original_atlases = true;
240
editor_file_dialog->popup_file_dialog();
241
}
242
}
243
244
bool AtlasMergingDialog::_set(const StringName &p_name, const Variant &p_value) {
245
if (p_name == "next_line_after_column" && p_value.get_type() == Variant::INT) {
246
next_line_after_column = p_value;
247
_update_texture();
248
return true;
249
}
250
return false;
251
}
252
253
bool AtlasMergingDialog::_get(const StringName &p_name, Variant &r_ret) const {
254
if (p_name == "next_line_after_column") {
255
r_ret = next_line_after_column;
256
return true;
257
}
258
return false;
259
}
260
261
void AtlasMergingDialog::_notification(int p_what) {
262
switch (p_what) {
263
case NOTIFICATION_VISIBILITY_CHANGED: {
264
if (is_visible()) {
265
_update_texture();
266
}
267
} break;
268
}
269
}
270
271
void AtlasMergingDialog::update_tile_set(Ref<TileSet> p_tile_set) {
272
ERR_FAIL_COND(p_tile_set.is_null());
273
tile_set = p_tile_set;
274
275
atlas_merging_atlases_list->clear();
276
for (int i = 0; i < p_tile_set->get_source_count(); i++) {
277
int source_id = p_tile_set->get_source_id(i);
278
Ref<TileSetAtlasSource> atlas_source = p_tile_set->get_source(source_id);
279
if (atlas_source.is_valid()) {
280
Ref<Texture2D> texture = atlas_source->get_texture();
281
if (texture.is_valid()) {
282
String item_text = vformat(TTR("%s (ID: %d)"), texture->get_path().get_file(), source_id);
283
atlas_merging_atlases_list->add_item(item_text, texture);
284
atlas_merging_atlases_list->set_item_metadata(-1, source_id);
285
}
286
}
287
}
288
289
get_ok_button()->set_disabled(true);
290
merge_button->set_disabled(true);
291
292
committed_actions_count = 0;
293
}
294
295
AtlasMergingDialog::AtlasMergingDialog() {
296
// Atlas merging window.
297
set_title(TTR("Atlas Merging"));
298
set_hide_on_ok(false);
299
300
// Ok buttons
301
set_ok_button_text(TTR("Merge (Keep original Atlases)"));
302
get_ok_button()->set_disabled(true);
303
merge_button = add_button(TTR("Merge"), true, "merge");
304
merge_button->set_disabled(true);
305
306
HSplitContainer *atlas_merging_h_split_container = memnew(HSplitContainer);
307
atlas_merging_h_split_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
308
atlas_merging_h_split_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
309
add_child(atlas_merging_h_split_container);
310
311
// Atlas sources item list.
312
atlas_merging_atlases_list = memnew(ItemList);
313
atlas_merging_atlases_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
314
atlas_merging_atlases_list->set_fixed_icon_size(Size2(60, 60) * EDSCALE);
315
atlas_merging_atlases_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
316
atlas_merging_atlases_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
317
atlas_merging_atlases_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
318
atlas_merging_atlases_list->set_custom_minimum_size(Size2(100, 200));
319
atlas_merging_atlases_list->set_select_mode(ItemList::SELECT_MULTI);
320
atlas_merging_atlases_list->set_theme_type_variation("ItemListSecondary");
321
atlas_merging_atlases_list->connect("multi_selected", callable_mp(this, &AtlasMergingDialog::_update_texture).unbind(2));
322
atlas_merging_h_split_container->add_child(atlas_merging_atlases_list);
323
324
VBoxContainer *atlas_merging_right_panel = memnew(VBoxContainer);
325
atlas_merging_right_panel->set_h_size_flags(Control::SIZE_EXPAND_FILL);
326
atlas_merging_right_panel->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
327
atlas_merging_h_split_container->add_child(atlas_merging_right_panel);
328
329
// Settings.
330
Label *settings_label = memnew(Label);
331
settings_label->set_text(TTR("Settings:"));
332
atlas_merging_right_panel->add_child(settings_label);
333
334
columns_editor_property = memnew(EditorPropertyInteger);
335
columns_editor_property->set_label(TTR("Next Line After Column"));
336
columns_editor_property->set_object_and_property(this, "next_line_after_column");
337
columns_editor_property->update_property();
338
columns_editor_property->connect("property_changed", callable_mp(this, &AtlasMergingDialog::_property_changed));
339
atlas_merging_right_panel->add_child(columns_editor_property);
340
341
// Preview.
342
Label *preview_label = memnew(Label);
343
preview_label->set_text(TTR("Preview:"));
344
atlas_merging_right_panel->add_child(preview_label);
345
346
preview = memnew(TextureRect);
347
preview->set_h_size_flags(Control::SIZE_EXPAND_FILL);
348
preview->set_v_size_flags(Control::SIZE_EXPAND_FILL);
349
preview->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
350
preview->hide();
351
preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
352
atlas_merging_right_panel->add_child(preview);
353
354
select_2_atlases_label = memnew(Label);
355
select_2_atlases_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
356
select_2_atlases_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
357
select_2_atlases_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
358
select_2_atlases_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
359
select_2_atlases_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
360
select_2_atlases_label->set_text(TTR("Please select two atlases or more."));
361
atlas_merging_right_panel->add_child(select_2_atlases_label);
362
363
// The file dialog to choose the texture path.
364
editor_file_dialog = memnew(EditorFileDialog);
365
editor_file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
366
editor_file_dialog->add_filter("*.png");
367
editor_file_dialog->connect("file_selected", callable_mp(this, &AtlasMergingDialog::_merge_confirmed));
368
add_child(editor_file_dialog);
369
}
370
371