/**************************************************************************/ /* visual_shader_editor_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 "visual_shader_editor_plugin.h" #include "core/config/project_settings.h" #include "core/io/resource_loader.h" #include "core/math/math_defs.h" #include "core/os/keyboard.h" #include "editor/editor_node.h" #include "editor/editor_properties.h" #include "editor/editor_properties_vector.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" #include "editor/filesystem_dock.h" #include "editor/inspector_dock.h" #include "editor/plugins/curve_editor_plugin.h" #include "editor/plugins/shader_editor_plugin.h" #include "editor/themes/editor_scale.h" #include "scene/gui/button.h" #include "scene/gui/check_box.h" #include "scene/gui/code_edit.h" #include "scene/gui/color_picker.h" #include "scene/gui/graph_edit.h" #include "scene/gui/menu_button.h" #include "scene/gui/option_button.h" #include "scene/gui/popup.h" #include "scene/gui/rich_text_label.h" #include "scene/gui/separator.h" #include "scene/gui/tree.h" #include "scene/gui/view_panner.h" #include "scene/main/window.h" #include "scene/resources/curve_texture.h" #include "scene/resources/image_texture.h" #include "scene/resources/style_box_flat.h" #include "scene/resources/visual_shader_nodes.h" #include "scene/resources/visual_shader_particle_nodes.h" #include "servers/display_server.h" #include "servers/rendering/shader_preprocessor.h" #include "servers/rendering/shader_types.h" struct FloatConstantDef { String name; float value = 0; String desc; }; static FloatConstantDef float_constant_defs[] = { { "E", Math_E, TTR("E constant (2.718282). Represents the base of the natural logarithm.") }, { "Epsilon", CMP_EPSILON, TTR("Epsilon constant (0.00001). Smallest possible scalar number.") }, { "Phi", 1.618034f, TTR("Phi constant (1.618034). Golden ratio.") }, { "Pi/4", Math_PI / 4, TTR("Pi/4 constant (0.785398) or 45 degrees.") }, { "Pi/2", Math_PI / 2, TTR("Pi/2 constant (1.570796) or 90 degrees.") }, { "Pi", Math_PI, TTR("Pi constant (3.141593) or 180 degrees.") }, { "Tau", Math_TAU, TTR("Tau constant (6.283185) or 360 degrees.") }, { "Sqrt2", Math_SQRT2, TTR("Sqrt2 constant (1.414214). Square root of 2.") } }; const int MAX_FLOAT_CONST_DEFS = sizeof(float_constant_defs) / sizeof(FloatConstantDef); /////////////////// void VisualShaderNodePlugin::set_editor(VisualShaderEditor *p_editor) { vseditor = p_editor; } Control *VisualShaderNodePlugin::create_editor(const Ref &p_parent_resource, const Ref &p_node) { Object *ret = nullptr; GDVIRTUAL_CALL(_create_editor, p_parent_resource, p_node, ret); return Object::cast_to(ret); } void VisualShaderNodePlugin::_bind_methods() { GDVIRTUAL_BIND(_create_editor, "parent_resource", "visual_shader_node"); } /////////////////// VisualShaderGraphPlugin::VisualShaderGraphPlugin() { vs_msdf_fonts_theme.instantiate(); } void VisualShaderGraphPlugin::_bind_methods() { ClassDB::bind_method("add_node", &VisualShaderGraphPlugin::add_node); ClassDB::bind_method("remove_node", &VisualShaderGraphPlugin::remove_node); ClassDB::bind_method("connect_nodes", &VisualShaderGraphPlugin::connect_nodes); ClassDB::bind_method("disconnect_nodes", &VisualShaderGraphPlugin::disconnect_nodes); ClassDB::bind_method("set_node_position", &VisualShaderGraphPlugin::set_node_position); ClassDB::bind_method("update_node", &VisualShaderGraphPlugin::update_node); ClassDB::bind_method("update_node_deferred", &VisualShaderGraphPlugin::update_node_deferred); ClassDB::bind_method("set_input_port_default_value", &VisualShaderGraphPlugin::set_input_port_default_value); ClassDB::bind_method("set_parameter_name", &VisualShaderGraphPlugin::set_parameter_name); ClassDB::bind_method("set_expression", &VisualShaderGraphPlugin::set_expression); ClassDB::bind_method("update_curve", &VisualShaderGraphPlugin::update_curve); ClassDB::bind_method("update_curve_xyz", &VisualShaderGraphPlugin::update_curve_xyz); ClassDB::bind_method(D_METHOD("attach_node_to_frame", "type", "id", "frame"), &VisualShaderGraphPlugin::attach_node_to_frame); ClassDB::bind_method(D_METHOD("detach_node_from_frame", "type", "id"), &VisualShaderGraphPlugin::detach_node_from_frame); ClassDB::bind_method(D_METHOD("set_frame_color_enabled", "type", "id", "enabled"), &VisualShaderGraphPlugin::set_frame_color_enabled); ClassDB::bind_method(D_METHOD("set_frame_color", "type", "id", "color"), &VisualShaderGraphPlugin::set_frame_color); ClassDB::bind_method(D_METHOD("set_frame_autoshrink_enabled", "type", "id", "enabled"), &VisualShaderGraphPlugin::set_frame_autoshrink_enabled); } void VisualShaderGraphPlugin::set_editor(VisualShaderEditor *p_editor) { editor = p_editor; } void VisualShaderGraphPlugin::register_shader(VisualShader *p_shader) { visual_shader = Ref(p_shader); } void VisualShaderGraphPlugin::set_connections(const List &p_connections) { connections = p_connections; } void VisualShaderGraphPlugin::show_port_preview(VisualShader::Type p_type, int p_node_id, int p_port_id, bool p_is_valid) { if (visual_shader->get_shader_type() == p_type && links.has(p_node_id) && links[p_node_id].output_ports.has(p_port_id)) { Link &link = links[p_node_id]; for (const KeyValue &E : link.output_ports) { if (E.value.preview_button != nullptr) { E.value.preview_button->set_pressed(false); } } bool is_dirty = link.preview_pos < 0; if (!is_dirty && link.preview_visible && link.preview_box != nullptr) { link.graph_element->remove_child(link.preview_box); memdelete(link.preview_box); link.preview_box = nullptr; link.graph_element->reset_size(); link.preview_visible = false; } if (p_port_id != -1 && link.output_ports[p_port_id].preview_button != nullptr) { if (is_dirty) { link.preview_pos = link.graph_element->get_child_count(); } VBoxContainer *vbox = memnew(VBoxContainer); link.graph_element->add_child(vbox); link.graph_element->move_child(vbox, link.preview_pos); GraphNode *graph_node = Object::cast_to(link.graph_element); if (graph_node) { graph_node->set_slot_draw_stylebox(vbox->get_index(false), false); } Control *offset = memnew(Control); offset->set_custom_minimum_size(Size2(0, 5 * EDSCALE)); vbox->add_child(offset); VisualShaderNodePortPreview *port_preview = memnew(VisualShaderNodePortPreview); port_preview->setup(visual_shader, visual_shader->get_shader_type(), p_node_id, p_port_id, p_is_valid); port_preview->set_h_size_flags(Control::SIZE_SHRINK_CENTER); vbox->add_child(port_preview); link.preview_visible = true; link.preview_box = vbox; link.output_ports[p_port_id].preview_button->set_pressed(true); } } } void VisualShaderGraphPlugin::update_node_deferred(VisualShader::Type p_type, int p_node_id) { callable_mp(this, &VisualShaderGraphPlugin::update_node).call_deferred(p_type, p_node_id); } void VisualShaderGraphPlugin::update_node(VisualShader::Type p_type, int p_node_id) { if (p_type != visual_shader->get_shader_type() || !links.has(p_node_id)) { return; } remove_node(p_type, p_node_id, true); add_node(p_type, p_node_id, true, true); // TODO: Restore focus here? } void VisualShaderGraphPlugin::set_input_port_default_value(VisualShader::Type p_type, int p_node_id, int p_port_id, const Variant &p_value) { if (p_type != visual_shader->get_shader_type() || !links.has(p_node_id)) { return; } Button *button = links[p_node_id].input_ports[p_port_id].default_input_button; switch (p_value.get_type()) { case Variant::COLOR: { button->set_custom_minimum_size(Size2(30, 0) * EDSCALE); Callable ce = callable_mp(editor, &VisualShaderEditor::_draw_color_over_button); if (!button->is_connected("draw", ce)) { button->connect("draw", ce.bind(button, p_value)); } } break; case Variant::BOOL: { button->set_text(((bool)p_value) ? "true" : "false"); } break; case Variant::INT: case Variant::FLOAT: { button->set_text(String::num(p_value, 4)); } break; case Variant::VECTOR2: { Vector2 v = p_value; button->set_text(String::num(v.x, 3) + "," + String::num(v.y, 3)); } break; case Variant::VECTOR3: { Vector3 v = p_value; button->set_text(String::num(v.x, 3) + "," + String::num(v.y, 3) + "," + String::num(v.z, 3)); } break; case Variant::QUATERNION: { Quaternion v = p_value; button->set_text(String::num(v.x, 3) + "," + String::num(v.y, 3) + "," + String::num(v.z, 3) + "," + String::num(v.w, 3)); } break; default: { } } } void VisualShaderGraphPlugin::set_parameter_name(VisualShader::Type p_type, int p_node_id, const String &p_name) { if (visual_shader->get_shader_type() == p_type && links.has(p_node_id) && links[p_node_id].parameter_name != nullptr) { links[p_node_id].parameter_name->set_text(p_name); } } void VisualShaderGraphPlugin::update_curve(int p_node_id) { if (links.has(p_node_id) && links[p_node_id].curve_editors[0]) { Ref tex = Object::cast_to(links[p_node_id].visual_node); ERR_FAIL_COND(!tex.is_valid()); if (tex->get_texture().is_valid()) { links[p_node_id].curve_editors[0]->set_curve(tex->get_texture()->get_curve()); } tex->emit_changed(); } } void VisualShaderGraphPlugin::update_curve_xyz(int p_node_id) { if (links.has(p_node_id) && links[p_node_id].curve_editors[0] && links[p_node_id].curve_editors[1] && links[p_node_id].curve_editors[2]) { Ref tex = Object::cast_to(links[p_node_id].visual_node); ERR_FAIL_COND(!tex.is_valid()); if (tex->get_texture().is_valid()) { links[p_node_id].curve_editors[0]->set_curve(tex->get_texture()->get_curve_x()); links[p_node_id].curve_editors[1]->set_curve(tex->get_texture()->get_curve_y()); links[p_node_id].curve_editors[2]->set_curve(tex->get_texture()->get_curve_z()); } tex->emit_changed(); } } int VisualShaderGraphPlugin::get_constant_index(float p_constant) const { for (int i = 0; i < MAX_FLOAT_CONST_DEFS; i++) { if (Math::is_equal_approx(p_constant, float_constant_defs[i].value)) { return i + 1; } } return 0; } void VisualShaderGraphPlugin::set_expression(VisualShader::Type p_type, int p_node_id, const String &p_expression) { if (p_type != visual_shader->get_shader_type() || !links.has(p_node_id) || !links[p_node_id].expression_edit) { return; } links[p_node_id].expression_edit->set_text(p_expression); } void VisualShaderGraphPlugin::attach_node_to_frame(VisualShader::Type p_type, int p_node_id, int p_frame_id) { if (p_type != visual_shader->get_shader_type() || !links.has(p_node_id) || !links.has(p_frame_id)) { return; } GraphEdit *graph = editor->graph; if (!graph) { return; } // Get the hint label and hide it before attaching the node to prevent resizing issues with the frame. GraphFrame *frame = Object::cast_to(links[p_frame_id].graph_element); ERR_FAIL_COND_MSG(!frame, "VisualShader node to attach to is not a frame node."); Label *frame_hint_label = Object::cast_to