Skip to content

Commit

Permalink
Implement baking of noise textures
Browse files Browse the repository at this point in the history
  • Loading branch information
cfare committed Mar 4, 2025
1 parent 1753893 commit 0ff3e25
Show file tree
Hide file tree
Showing 9 changed files with 360 additions and 6 deletions.
5 changes: 5 additions & 0 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
#include "editor/plugins/material_editor_plugin.h"
#include "editor/plugins/mesh_library_editor_plugin.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "editor/plugins/noise_texture_conversion_plugin.h"
#include "editor/plugins/packed_scene_translation_parser_plugin.h"
#include "editor/plugins/particle_process_material_editor_plugin.h"
#include "editor/plugins/plugin_config_dialog.h"
Expand Down Expand Up @@ -7972,6 +7973,10 @@ EditorNode::EditorNode() {
Ref<VisualShaderConversionPlugin> vshader_convert;
vshader_convert.instantiate();
resource_conversion_plugins.push_back(vshader_convert);

Ref<NoiseTextureConversionPlugin> noise_tex_convert;
noise_tex_convert.instantiate();
resource_conversion_plugins.push_back(noise_tex_convert);
}

update_spinner_step_msec = OS::get_singleton()->get_ticks_msec();
Expand Down
16 changes: 14 additions & 2 deletions editor/editor_resource_picker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,11 @@ void EditorResourcePicker::_update_menu_items() {
}
}

void EditorResourcePicker::_edit_convert_resource_complete(Ref<Resource> p_new_resource) {
edited_resource = p_new_resource;
_resource_changed();
}

void EditorResourcePicker::_edit_menu_cbk(int p_which) {
switch (p_which) {
case OBJ_MENU_LOAD: {
Expand Down Expand Up @@ -454,8 +459,15 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
Vector<Ref<EditorResourceConversionPlugin>> conversions = EditorNode::get_singleton()->find_resource_conversion_plugin_for_resource(edited_resource);
ERR_FAIL_INDEX(to_type, conversions.size());

edited_resource = conversions[to_type]->convert(edited_resource);
_resource_changed();
Ref<Resource> converted_resource = conversions[to_type]->convert(edited_resource);
if (!converted_resource.is_null()) {
edited_resource = converted_resource;
_resource_changed();
} else {
Callable convert_callback = callable_mp(this, &EditorResourcePicker::_edit_convert_resource_complete);
conversions[to_type]->convert_async(edited_resource, convert_callback);
}

break;
}

Expand Down
1 change: 1 addition & 0 deletions editor/editor_resource_picker.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class EditorResourcePicker : public HBoxContainer {

void _update_menu();
void _update_menu_items();
void _edit_convert_resource_complete(Ref<Resource> p_new_resource);
void _edit_menu_cbk(int p_which);

void _button_draw();
Expand Down
23 changes: 19 additions & 4 deletions editor/filesystem_dock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1863,6 +1863,15 @@ void FileSystemDock::_overwrite_dialog_action(bool p_overwrite) {
_move_operation_confirm(to_move_path, to_move_or_copy, p_overwrite ? OVERWRITE_REPLACE : OVERWRITE_RENAME);
}

void FileSystemDock::_convert_async_cbk(Ref<Resource> p_new_res, Ref<Resource> p_old_res) {
EditorNode::get_singleton()->replace_resources_in_scenes({ p_old_res }, { p_new_res });

const String old_res_path = p_old_res->get_path();
const String rmpath = OS::get_singleton()->get_resource_dir() + old_res_path.replace_first("res://", "/");
OS::get_singleton()->move_to_trash(rmpath);
EditorFileSystem::get_singleton()->update_file(old_res_path);
}

void FileSystemDock::_convert_dialog_action() {
Vector<Ref<Resource>> selected_resources;
for (const String &S : to_convert) {
Expand All @@ -1879,11 +1888,17 @@ void FileSystemDock::_convert_dialog_action() {
int conversion_id = 0;
for (const String &target : cached_valid_conversion_targets) {
if (conversion_id == selected_conversion_id && conversion->converts_to() == target) {
Ref<Resource> converted_res = conversion->convert(res);
ERR_FAIL_COND(res.is_null());
converted_resources.push_back(converted_res);
resources_to_erase_history_for.insert(res);
break;
Ref<Resource> converted_res = conversion->convert(res);
if (!converted_res.is_null()) {
converted_resources.push_back(converted_res);
resources_to_erase_history_for.insert(res);
break;
} else {
Callable cbk = callable_mp(this, &FileSystemDock::_convert_async_cbk).bind(res);
conversion->convert_async(res, cbk);
break;
}
}
conversion_id++;
}
Expand Down
1 change: 1 addition & 0 deletions editor/filesystem_dock.h
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ class FileSystemDock : public VBoxContainer {
void _rename_operation_confirm();
void _duplicate_operation_confirm(const String &p_path);
void _overwrite_dialog_action(bool p_overwrite);
void _convert_async_cbk(Ref<Resource> p_old_res, Ref<Resource> p_new_res);
void _convert_dialog_action();
Vector<String> _check_existing();
void _move_operation_confirm(const String &p_to_path, bool p_copy = false, Overwrite p_overwrite = OVERWRITE_UNDECIDED);
Expand Down
7 changes: 7 additions & 0 deletions editor/plugins/editor_resource_conversion_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ void EditorResourceConversionPlugin::_bind_methods() {
GDVIRTUAL_BIND(_converts_to);
GDVIRTUAL_BIND(_handles, "resource");
GDVIRTUAL_BIND(_convert, "resource");
GDVIRTUAL_BIND(_convert_async, "resource", "on_complete");
}

String EditorResourceConversionPlugin::converts_to() const {
Expand All @@ -53,3 +54,9 @@ Ref<Resource> EditorResourceConversionPlugin::convert(const Ref<Resource> &p_res
GDVIRTUAL_CALL(_convert, p_resource, ret);
return ret;
}

bool EditorResourceConversionPlugin::convert_async(const Ref<Resource> &p_resource, const Callable &p_on_complete) {
bool ret = false;
GDVIRTUAL_CALL(_convert_async, p_resource, p_on_complete, ret);
return ret;
}
2 changes: 2 additions & 0 deletions editor/plugins/editor_resource_conversion_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ class EditorResourceConversionPlugin : public RefCounted {
GDVIRTUAL0RC(String, _converts_to)
GDVIRTUAL1RC(bool, _handles, Ref<Resource>)
GDVIRTUAL1RC(Ref<Resource>, _convert, Ref<Resource>)
GDVIRTUAL2RC(bool, _convert_async, Ref<Resource>, Callable)

public:
virtual String converts_to() const;
virtual bool handles(const Ref<Resource> &p_resource) const;
virtual Ref<Resource> convert(const Ref<Resource> &p_resource) const;
virtual bool convert_async(const Ref<Resource> &p_resource, const Callable &p_on_complete);
};

#endif // EDITOR_RESOURCE_CONVERSION_PLUGIN_H
212 changes: 212 additions & 0 deletions editor/plugins/noise_texture_conversion_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/**************************************************************************/
/* noise_texture_conversion_plugin.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "noise_texture_conversion_plugin.h"

#include "core/config/project_settings.h"
#include "core/io/resource_importer.h"
#include "editor/editor_file_system.h"
#include "editor/editor_string_names.h"
#include "editor/gui/editor_file_dialog.h"
#include "editor/gui/editor_validation_panel.h"
#include "editor/themes/editor_scale.h"
#include "modules/noise/noise_texture_2d.h"
#include "scene/gui/check_box.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/grid_container.h"
#include "scene/gui/line_edit.h"
#include "scene/resources/image_texture.h"

String NoiseTextureConversionPlugin::converts_to() const {
return "CompressedTexture2D";
}

bool NoiseTextureConversionPlugin::handles(const Ref<Resource> &p_resource) const {
Ref<NoiseTexture2D> mat = p_resource;
return mat.is_valid();
}

void ConvertTextureDialog::_check_file_path() {
validation_panel->set_message(MSG_ID_INFO_0, TTR("The image data from the resource will be saved as a PNG to the path specified."), EditorValidationPanel::MSG_INFO);
validation_panel->set_message(MSG_ID_INFO_1, TTR("The new image will be imported as a CompressedTexture2D which will replace the original resource."), EditorValidationPanel::MSG_INFO);

String file_path_text = file_path->get_text().strip_edges();
if (file_path_text.is_empty()) {
validation_panel->set_message(MSG_ID_PATH, TTR("Image path is empty."), EditorValidationPanel::MSG_ERROR);
return;
} else if (file_path_text.get_file().get_basename().is_empty()) {
validation_panel->set_message(MSG_ID_PATH, TTR("Image file name is empty."), EditorValidationPanel::MSG_ERROR);
return;
} else if (!file_path_text.get_file().get_basename().is_valid_filename()) {
validation_panel->set_message(MSG_ID_PATH, TTR("Image file name is invalid."), EditorValidationPanel::MSG_ERROR);
return;
}
file_path_text = ProjectSettings::get_singleton()->localize_path(file_path_text);
if (!file_path_text.begins_with("res://")) {
validation_panel->set_message(MSG_ID_PATH, TTR("Path is not local."), EditorValidationPanel::MSG_ERROR);
} else {
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
if (da->dir_exists(file_path_text) || da->file_exists(file_path_text)) {
validation_panel->set_message(MSG_ID_PATH, TTR("A file or directory with the same name exists."), EditorValidationPanel::MSG_ERROR);
}
if (!da->dir_exists(file_path_text.get_base_dir())) {
validation_panel->set_message(MSG_ID_PATH, TTR("Parent directory does not exist."), EditorValidationPanel::MSG_ERROR);
}
}
}

void ConvertTextureDialog::_browse_path_selected(String selected_path) {
file_path->set_text(selected_path);
validation_panel->update();
}

void ConvertTextureDialog::_browse_path() {
file_browse->set_current_path(file_path->get_text());
file_browse->popup_file_dialog();
}

ConvertTextureDialog::ConvertTextureDialog() {
GridContainer *gc = memnew(GridContainer);
gc->set_columns(2);

validation_panel = memnew(EditorValidationPanel);
validation_panel->add_line(MSG_ID_PATH, TTR("Image path/name is valid."));
validation_panel->add_line(MSG_ID_INFO_0);
validation_panel->add_line(MSG_ID_INFO_1);
validation_panel->set_update_callback(callable_mp(this, &ConvertTextureDialog::_check_file_path));
validation_panel->set_accept_button(get_ok_button());
validation_panel->update();

VBoxContainer *vb = memnew(VBoxContainer);
vb->add_child(gc);
vb->add_child(validation_panel);
add_child(vb);

HBoxContainer *hb = memnew(HBoxContainer);
file_path = memnew(LineEdit);
file_path->connect(SceneStringName(text_changed), callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1));
file_path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
hb->add_child(file_path);
hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
register_text_enter(file_path);
path_button = memnew(Button);

path_button->connect(SceneStringName(pressed), callable_mp(this, &ConvertTextureDialog::_browse_path));
hb->add_child(path_button);
Label *label = memnew(Label(TTR("New Image Path:")));
gc->add_child(label);
gc->add_child(hb);

gc->set_custom_minimum_size(Size2(650, 0) * EDSCALE);
set_title(TTR("Convert to CompressedTexture2D"));

file_browse = memnew(EditorFileDialog);
file_browse->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
// We don't want to allow overwriting, but that gets handled in the main dialog.
file_browse->set_disable_overwrite_warning(true);
file_browse->set_filters({ "*.png" });
file_browse->connect(SNAME("file_selected"), callable_mp(this, &ConvertTextureDialog::_browse_path_selected));
add_child(file_browse);
}

ConvertTextureDialog *NoiseTextureConversionPlugin::create_confirmation_dialog() {
dialog = memnew(ConvertTextureDialog);
dialog->connect(SceneStringName(confirmed), callable_mp(this, &NoiseTextureConversionPlugin::_confirm_conversion));
return dialog;
}

void ConvertTextureDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
path_button->set_button_icon(get_editor_theme_icon(SNAME("Folder")));
} break;
}
}

void ConvertTextureDialog::config(const Ref<Resource> &p_resource) {
resource = p_resource;
file_path->set_text(resource->get_path().get_basename() + ".png");
}

void NoiseTextureConversionPlugin::_confirm_conversion() {
Ref<NoiseTexture2D> noise_tex = resource;
if (noise_tex.is_null()) {
callback.call(Ref<Resource>({}));
return;
}

auto base_image = noise_tex->get_image();
base_image->save_png(dialog->get_file_path());
base_image->notify_property_list_changed();
pending_updates.append({ callback, dialog->get_file_path() });
EditorFileSystem::get_singleton()->scan_changes();

return;
}

void NoiseTextureConversionPlugin::_on_filesystem_updated() {
// Keep track of resource updates that haven't completed yet.
Vector<PendingResourceUpdate> remaining_updates;

for (PendingResourceUpdate &update : pending_updates) {
Ref<Resource> new_img = ResourceLoader::load(dialog->get_file_path(), "image");
if (new_img.is_null()) {
remaining_updates.push_back(update);
} else {
update.cb.call(new_img);
}
}
pending_updates = remaining_updates;
if (pending_updates.size() == 0) {
EditorFileSystem::get_singleton()->disconnect(SNAME("filesystem_changed"),
callable_mp(this, &NoiseTextureConversionPlugin::_on_filesystem_updated));
}
}

bool NoiseTextureConversionPlugin::convert_async(const Ref<Resource> &p_resource, const Callable &p_on_complete) {
resource = p_resource;
callback = p_on_complete;
EditorNode::get_singleton()->add_child(create_confirmation_dialog());
dialog->popup_centered();
dialog->config(p_resource);

// The callback may already be set by an in-flight conversion.
Callable cb = callable_mp(this, &NoiseTextureConversionPlugin::_on_filesystem_updated);
if (!EditorFileSystem::get_singleton()->is_connected(SNAME("filesystem_changed"), cb)) {
EditorFileSystem::get_singleton()->connect(SNAME("filesystem_changed"), cb);
}

return true;
}

Ref<Resource> NoiseTextureConversionPlugin::convert(const Ref<Resource> &p_resource) const {
// Synchronous conversions not supported.
return nullptr;
}
Loading

0 comments on commit 0ff3e25

Please sign in to comment.