diff --git a/editor/voxel_graph_editor.cpp b/editor/voxel_graph_editor.cpp index 0b22bdfd..d635ed05 100644 --- a/editor/voxel_graph_editor.cpp +++ b/editor/voxel_graph_editor.cpp @@ -1,6 +1,9 @@ #include "voxel_graph_editor.h" #include "../generators/graph/voxel_generator_graph.h" #include "editor/editor_scale.h" + +#include +#include #include #include #include @@ -8,11 +11,44 @@ const char *VoxelGraphEditor::SIGNAL_NODE_SELECTED = "node_selected"; const char *VoxelGraphEditor::SIGNAL_NOTHING_SELECTED = "nothing_selected"; +// Shows a 2D slice of the 3D set of values coming from an output port +class VoxelGraphEditorNodePreview : public VBoxContainer { + GDCLASS(VoxelGraphEditorNodePreview, VBoxContainer) +public: + static const int RESOLUTION = 64; + + VoxelGraphEditorNodePreview() { + _image.instance(); + _image->create(RESOLUTION, RESOLUTION, false, Image::FORMAT_L8); + _texture.instance(); + update_texture(); + _texture_rect = memnew(TextureRect); + _texture_rect->set_stretch_mode(TextureRect::STRETCH_SCALE); + _texture_rect->set_custom_minimum_size(Vector2(RESOLUTION, RESOLUTION)); + _texture_rect->set_texture(_texture); + add_child(_texture_rect); + } + + Ref get_image() const { + return _image; + } + + void update_texture() { + _texture->create_from_image(_image, 0); + } + +private: + TextureRect *_texture_rect = nullptr; + Ref _texture; + Ref _image; +}; + // Graph node with a few custom data attached. class VoxelGraphEditorNode : public GraphNode { GDCLASS(VoxelGraphEditorNode, GraphNode) public: uint32_t node_id = 0; + VoxelGraphEditorNodePreview *preview = nullptr; }; VoxelGraphEditor::VoxelGraphEditor() { @@ -43,13 +79,15 @@ void VoxelGraphEditor::set_graph(Ref graph) { return; } - // if (_graph.is_valid()) { - // } + if (_graph.is_valid()) { + _graph->disconnect(CoreStringNames::get_singleton()->changed, this, "_on_graph_changed"); + } _graph = graph; - // if (_graph.is_valid()) { - // } + if (_graph.is_valid()) { + _graph->connect(CoreStringNames::get_singleton()->changed, this, "_on_graph_changed"); + } build_gui_from_graph(); } @@ -58,6 +96,27 @@ void VoxelGraphEditor::set_undo_redo(UndoRedo *undo_redo) { _undo_redo = undo_redo; } +void VoxelGraphEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_INTERNAL_PROCESS: + _process(get_tree()->get_idle_process_time()); + break; + + case NOTIFICATION_VISIBILITY_CHANGED: + set_process_internal(is_visible()); + break; + } +} + +void VoxelGraphEditor::_process(float delta) { + if (_time_before_preview_update > 0.f) { + _time_before_preview_update -= delta; + if (_time_before_preview_update < 0.f) { + update_previews(); + } + } +} + void VoxelGraphEditor::clear() { _graph_edit->clear_connections(); for (int i = 0; i < _graph_edit->get_child_count(); ++i) { @@ -164,6 +223,11 @@ void VoxelGraphEditor::create_node_gui(uint32_t node_id) { node_view->set_slot(i, has_left, Variant::REAL, port_color, has_right, Variant::REAL, port_color); } + if (node_type_id == VoxelGeneratorGraph::NODE_SDF_PREVIEW) { + node_view->preview = memnew(VoxelGraphEditorNodePreview); + node_view->add_child(node_view->preview); + } + _graph_edit->add_child(node_view); } @@ -386,6 +450,89 @@ void VoxelGraphEditor::_check_nothing_selected() { } } +void VoxelGraphEditor::update_previews() { + if (_graph.is_null()) { + return; + } + + uint64_t time_before = OS::get_singleton()->get_ticks_usec(); + + _graph->compile(); + + // TODO Use a thread? + print_line("Updating previews"); + + struct PreviewInfo { + VoxelGraphEditorNodePreview *control; + uint16_t address; + float min_value; + float value_scale; + }; + + std::vector previews; + const VoxelGraphRuntime &runtime = _graph->get_runtime(); + + for (int i = 0; i < _graph_edit->get_child_count(); ++i) { + VoxelGraphEditorNode *node = Object::cast_to(_graph_edit->get_child(i)); + if (node == nullptr || node->preview == nullptr) { + continue; + } + ProgramGraph::PortLocation dst; + dst.node_id = node->node_id; + dst.port_index = 0; + ProgramGraph::PortLocation src; + if (!_graph->try_get_connection_to(dst, src)) { + // Not connected? + continue; + } + PreviewInfo info; + info.control = node->preview; + info.address = runtime.get_output_port_address(src); + info.min_value = _graph->get_node_param(dst.node_id, 0); + const float max_value = _graph->get_node_param(dst.node_id, 1); + info.value_scale = 1.f / (max_value - info.min_value); + previews.push_back(info); + } + + for (size_t i = 0; i < previews.size(); ++i) { + previews[i].control->get_image()->lock(); + } + + for (int iy = 0; iy < VoxelGraphEditorNodePreview::RESOLUTION; ++iy) { + for (int ix = 0; ix < VoxelGraphEditorNodePreview::RESOLUTION; ++ix) { + { + const int x = ix - VoxelGraphEditorNodePreview::RESOLUTION / 2; + const int y = (VoxelGraphEditorNodePreview::RESOLUTION - iy) - VoxelGraphEditorNodePreview::RESOLUTION / 2; + _graph->generate_single(Vector3i(x, y, 0)); + } + + for (size_t i = 0; i < previews.size(); ++i) { + PreviewInfo &info = previews[i]; + const float v = runtime.get_memory_value(info.address); + const float g = clamp((v - info.min_value) * info.value_scale, 0.f, 1.f); + Color c(g, g, g); + info.control->get_image()->set_pixel(ix, iy, Color(g, g, g)); + } + } + } + + for (size_t i = 0; i < previews.size(); ++i) { + previews[i].control->get_image()->unlock(); + previews[i].control->update_texture(); + } + + uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - time_before; + print_line(String("Previews generated in {0} us").format(varray(time_taken))); +} + +void VoxelGraphEditor::schedule_preview_update() { + _time_before_preview_update = 0.5f; +} + +void VoxelGraphEditor::_on_graph_changed() { + schedule_preview_update(); +} + void VoxelGraphEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_on_graph_edit_gui_input", "event"), &VoxelGraphEditor::_on_graph_edit_gui_input); ClassDB::bind_method(D_METHOD("_on_graph_edit_connection_request", "from_node_name", "from_slot", "to_node_name", "to_slot"), @@ -397,6 +544,8 @@ void VoxelGraphEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_on_graph_edit_node_unselected"), &VoxelGraphEditor::_on_graph_edit_node_unselected); ClassDB::bind_method(D_METHOD("_on_graph_node_dragged", "from", "to", "id"), &VoxelGraphEditor::_on_graph_node_dragged); ClassDB::bind_method(D_METHOD("_on_context_menu_index_pressed", "idx"), &VoxelGraphEditor::_on_context_menu_index_pressed); + ClassDB::bind_method(D_METHOD("_on_graph_changed"), &VoxelGraphEditor::_on_graph_changed); + ClassDB::bind_method(D_METHOD("_check_nothing_selected"), &VoxelGraphEditor::_check_nothing_selected); ClassDB::bind_method(D_METHOD("create_node_gui", "node_id"), &VoxelGraphEditor::create_node_gui); diff --git a/editor/voxel_graph_editor.h b/editor/voxel_graph_editor.h index 6bcf3701..543cd5b1 100644 --- a/editor/voxel_graph_editor.h +++ b/editor/voxel_graph_editor.h @@ -23,12 +23,18 @@ public: void set_undo_redo(UndoRedo *undo_redo); private: + void _notification(int p_what); + void _process(float delta); + void clear(); void build_gui_from_graph(); void create_node_gui(uint32_t node_id); void remove_node_gui(StringName gui_node_name); void set_node_position(int id, Vector2 offset); + void schedule_preview_update(); + void update_previews(); + void _on_graph_edit_gui_input(Ref event); void _on_graph_edit_connection_request(String from_node_name, int from_slot, String to_node_name, int to_slot); void _on_graph_edit_disconnection_request(String from_node_name, int from_slot, String to_node_name, int to_slot); @@ -37,6 +43,7 @@ private: void _on_graph_edit_node_unselected(Node *p_node); void _on_graph_node_dragged(Vector2 from, Vector2 to, int id); void _on_context_menu_index_pressed(int idx); + void _on_graph_changed(); void _check_nothing_selected(); @@ -48,6 +55,7 @@ private: UndoRedo *_undo_redo = nullptr; Vector2 _click_position; bool _nothing_selected_check_scheduled = false; + float _time_before_preview_update = 0.f; }; #endif // VOXEL_GRAPH_EDITOR_H diff --git a/generators/graph/program_graph.cpp b/generators/graph/program_graph.cpp index 26a99216..725c05ba 100644 --- a/generators/graph/program_graph.cpp +++ b/generators/graph/program_graph.cpp @@ -116,6 +116,7 @@ bool ProgramGraph::can_connect(PortLocation src, PortLocation dst) const { return false; } const Node *dst_node = get_node(dst.node_id); + // There can be only one connection from a source to a destination return dst_node->inputs[dst.port_index].connections.size() == 0; } diff --git a/generators/graph/voxel_generator_graph.cpp b/generators/graph/voxel_generator_graph.cpp index 97f76e4e..e344fd98 100644 --- a/generators/graph/voxel_generator_graph.cpp +++ b/generators/graph/voxel_generator_graph.cpp @@ -2,6 +2,8 @@ #include "../../util/profiling_clock.h" #include "voxel_graph_node_db.h" +#include + VoxelGeneratorGraph::VoxelGeneratorGraph() { clear(); clear_bounds(); @@ -47,6 +49,7 @@ ProgramGraph::Node *VoxelGeneratorGraph::create_node_internal(NodeTypeID type_id void VoxelGeneratorGraph::remove_node(uint32_t node_id) { _graph.remove_node(node_id); + emit_changed(); } bool VoxelGeneratorGraph::can_connect(uint32_t src_node_id, uint32_t src_port_index, uint32_t dst_node_id, uint32_t dst_port_index) const { @@ -59,12 +62,14 @@ void VoxelGeneratorGraph::add_connection(uint32_t src_node_id, uint32_t src_port _graph.connect( ProgramGraph::PortLocation{ src_node_id, src_port_index }, ProgramGraph::PortLocation{ dst_node_id, dst_port_index }); + emit_changed(); } void VoxelGeneratorGraph::remove_connection(uint32_t src_node_id, uint32_t src_port_index, uint32_t dst_node_id, uint32_t dst_port_index) { _graph.disconnect( ProgramGraph::PortLocation{ src_node_id, src_port_index }, ProgramGraph::PortLocation{ dst_node_id, dst_port_index }); + emit_changed(); } void VoxelGeneratorGraph::get_connections(std::vector &connections) const { @@ -75,6 +80,18 @@ void VoxelGeneratorGraph::get_connections(std::vector // _graph.get_connections_from_and_to(connections, node_id); //} +bool VoxelGeneratorGraph::try_get_connection_to(ProgramGraph::PortLocation dst, ProgramGraph::PortLocation &out_src) const { + const ProgramGraph::Node *node = _graph.get_node(dst.node_id); + CRASH_COND(node == nullptr); + CRASH_COND(dst.port_index >= node->inputs.size()); + const ProgramGraph::Port &port = node->inputs[dst.port_index]; + if (port.connections.size() == 0) { + return false; + } + out_src = port.connections[0]; + return true; +} + bool VoxelGeneratorGraph::has_node(uint32_t node_id) const { return _graph.try_get_node(node_id) != nullptr; } @@ -83,7 +100,24 @@ void VoxelGeneratorGraph::set_node_param(uint32_t node_id, uint32_t param_index, ProgramGraph::Node *node = _graph.try_get_node(node_id); ERR_FAIL_COND(node == nullptr); ERR_FAIL_INDEX(param_index, node->params.size()); - node->params[param_index] = value; + + // TODO Changing sub-resources won't trigger a change signal + // It's actually very annoying to setup and keep correct. Needs to be done cautiously. + + // Ref res = node->params[param_index]; + // if (res.is_valid()) { + // res->disconnect(CoreStringNames::get_singleton()->changed, this, "_on_subresource_changed"); + // } + + if (node->params[param_index] != value) { + node->params[param_index] = value; + emit_changed(); + } + + // res = value; + // if (res.is_valid()) { + // res->connect(CoreStringNames::get_singleton()->changed, this, "_on_subresource_changed"); + // } } Variant VoxelGeneratorGraph::get_node_param(uint32_t node_id, uint32_t param_index) const { @@ -104,7 +138,10 @@ void VoxelGeneratorGraph::set_node_default_input(uint32_t node_id, uint32_t inpu ProgramGraph::Node *node = _graph.try_get_node(node_id); ERR_FAIL_COND(node == nullptr); ERR_FAIL_INDEX(input_index, node->default_inputs.size()); - node->default_inputs[input_index] = value; + if (node->default_inputs[input_index] != value) { + node->default_inputs[input_index] = value; + emit_changed(); + } } Vector2 VoxelGeneratorGraph::get_node_gui_position(uint32_t node_id) const { @@ -116,7 +153,11 @@ Vector2 VoxelGeneratorGraph::get_node_gui_position(uint32_t node_id) const { void VoxelGeneratorGraph::set_node_gui_position(uint32_t node_id, Vector2 pos) { ProgramGraph::Node *node = _graph.try_get_node(node_id); ERR_FAIL_COND(node == nullptr); - node->gui_position = pos; + if (node->gui_position != pos) { + node->gui_position = pos; + // Moving nodes around doesn't functionally change the graph + //emit_changed(); + } } VoxelGeneratorGraph::NodeTypeID VoxelGeneratorGraph::get_node_type_id(uint32_t node_id) const { @@ -658,6 +699,10 @@ float VoxelGeneratorGraph::_b_generate_single(Vector3 pos) { return generate_single(Vector3i(pos)); } +// void VoxelGeneratorGraph::_on_subresource_changed() { +// emit_changed(); +// } + void VoxelGeneratorGraph::_bind_methods() { ClassDB::bind_method(D_METHOD("clear"), &VoxelGeneratorGraph::clear); ClassDB::bind_method(D_METHOD("create_node", "type_id", "position", "id"), &VoxelGeneratorGraph::create_node, DEFVAL(ProgramGraph::NULL_ID)); @@ -690,6 +735,8 @@ void VoxelGeneratorGraph::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_graph_data", "data"), &VoxelGeneratorGraph::load_graph_from_variant_data); ClassDB::bind_method(D_METHOD("_get_graph_data"), &VoxelGeneratorGraph::get_graph_as_variant_data); + // ClassDB::bind_method(D_METHOD("_on_subresource_changed"), &VoxelGeneratorGraph::_on_subresource_changed); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "graph_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_graph_data", "_get_graph_data"); @@ -725,5 +772,6 @@ void VoxelGeneratorGraph::_bind_methods() { BIND_ENUM_CONSTANT(NODE_SDF_BOX); BIND_ENUM_CONSTANT(NODE_SDF_SPHERE); BIND_ENUM_CONSTANT(NODE_SDF_TORUS); + BIND_ENUM_CONSTANT(NODE_SDF_PREVIEW); BIND_ENUM_CONSTANT(NODE_TYPE_COUNT); } diff --git a/generators/graph/voxel_generator_graph.h b/generators/graph/voxel_generator_graph.h index 4159768b..9324054a 100644 --- a/generators/graph/voxel_generator_graph.h +++ b/generators/graph/voxel_generator_graph.h @@ -41,6 +41,7 @@ public: NODE_SDF_BOX, NODE_SDF_SPHERE, NODE_SDF_TORUS, + NODE_SDF_PREVIEW, // For debugging NODE_TYPE_COUNT }; @@ -57,6 +58,7 @@ public: void remove_connection(uint32_t src_node_id, uint32_t src_port_index, uint32_t dst_node_id, uint32_t dst_port_index); void get_connections(std::vector &connections) const; //void get_connections_from_and_to(std::vector &connections, uint32_t node_id) const; + bool try_get_connection_to(ProgramGraph::PortLocation dst, ProgramGraph::PortLocation &out_src) const; bool has_node(uint32_t node_id) const; @@ -91,13 +93,17 @@ public: Ref duplicate(bool p_subresources) const override; + // Internal + + const VoxelGraphRuntime &get_runtime() const { return _runtime; } + void compile(); + // Debug float debug_measure_microseconds_per_voxel(); void debug_load_waves_preset(); private: - void compile(); Interval analyze_range(Vector3i min_pos, Vector3i max_pos); ProgramGraph::Node *create_node_internal(NodeTypeID type_id, Vector2 position, uint32_t id); @@ -118,6 +124,9 @@ private: void _b_set_node_param_null(int node_id, int param_index); float _b_generate_single(Vector3 pos); + void _on_subresource_changed(); + void connect_to_subresource_changes(); + static void _bind_methods(); struct Bounds { diff --git a/generators/graph/voxel_graph_node_db.cpp b/generators/graph/voxel_graph_node_db.cpp index 829607ef..86f4953b 100644 --- a/generators/graph/voxel_graph_node_db.cpp +++ b/generators/graph/voxel_graph_node_db.cpp @@ -258,6 +258,14 @@ VoxelGraphNodeDB::VoxelGraphNodeDB() { t.inputs.push_back(Port("radius2", 4.f)); t.outputs.push_back(Port("sdf")); } + { + NodeType &t = types[VoxelGeneratorGraph::NODE_SDF_PREVIEW]; + t.name = "SdfPreview"; + t.inputs.push_back(Port("value")); + t.params.push_back(Param("min_value", Variant::REAL, -1.f)); + t.params.push_back(Param("max_value", Variant::REAL, 1.f)); + t.debug_only = true; + } for (unsigned int i = 0; i < _types.size(); ++i) { NodeType &t = _types[i]; diff --git a/generators/graph/voxel_graph_node_db.h b/generators/graph/voxel_graph_node_db.h index e0ebabb8..a4535c2b 100644 --- a/generators/graph/voxel_graph_node_db.h +++ b/generators/graph/voxel_graph_node_db.h @@ -40,6 +40,8 @@ public: struct NodeType { String name; + bool debug_only = false; + // TODO Category std::vector inputs; std::vector outputs; std::vector params; diff --git a/generators/graph/voxel_graph_runtime.cpp b/generators/graph/voxel_graph_runtime.cpp index 47ae68cd..20f65677 100644 --- a/generators/graph/voxel_graph_runtime.cpp +++ b/generators/graph/voxel_graph_runtime.cpp @@ -181,15 +181,23 @@ void VoxelGraphRuntime::clear() { _xzy_program_start = 0; _last_x = INT_MAX; _last_z = INT_MAX; + _output_port_addresses.clear(); } void VoxelGraphRuntime::compile(const ProgramGraph &graph) { + _output_port_addresses.clear(); + std::vector order; std::vector terminal_nodes; graph.find_terminal_nodes(terminal_nodes); - // For now only 1 end is supported - ERR_FAIL_COND(terminal_nodes.size() != 1); + + // Exclude debug nodes + unordered_remove_if(terminal_nodes, [&graph](uint32_t node_id) { + const ProgramGraph::Node *node = graph.get_node(node_id); + const VoxelGraphNodeDB::NodeType &type = VoxelGraphNodeDB::get_singleton()->get_type(node->type_id); + return type.debug_only; + }); graph.find_dependencies(terminal_nodes.back(), order); @@ -271,7 +279,6 @@ void VoxelGraphRuntime::compile(const ProgramGraph &graph) { std::vector &program = _program; const VoxelGraphNodeDB &type_db = *VoxelGraphNodeDB::get_singleton(); - HashMap output_port_addresses; bool has_output = false; // Run through each node in order, and turn them into program instructions @@ -294,19 +301,19 @@ void VoxelGraphRuntime::compile(const ProgramGraph &graph) { CRASH_COND(type.params.size() != 1); uint16_t a = _memory.size(); _memory.push_back(node->params[0].operator float()); - output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = a; + _output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = a; } break; case VoxelGeneratorGraph::NODE_INPUT_X: - output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = 0; + _output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = 0; break; case VoxelGeneratorGraph::NODE_INPUT_Y: - output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = 1; + _output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = 1; break; case VoxelGeneratorGraph::NODE_INPUT_Z: - output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = 2; + _output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = 2; break; case VoxelGeneratorGraph::NODE_OUTPUT_SDF: @@ -337,7 +344,7 @@ void VoxelGraphRuntime::compile(const ProgramGraph &graph) { } else { ProgramGraph::PortLocation src_port = node->inputs[j].connections[0]; - const uint16_t *aptr = output_port_addresses.getptr(src_port); + const uint16_t *aptr = _output_port_addresses.getptr(src_port); // Previous node ports must have been registered CRASH_COND(aptr == nullptr); a = *aptr; @@ -353,7 +360,7 @@ void VoxelGraphRuntime::compile(const ProgramGraph &graph) { // This will be used by next nodes const ProgramGraph::PortLocation op{ node_id, static_cast(j) }; - output_port_addresses[op] = a; + _output_port_addresses[op] = a; append(program, a); } @@ -980,3 +987,14 @@ Interval VoxelGraphRuntime::analyze_range(Vector3i min_pos, Vector3i max_pos) { return Interval(min_memory[min_memory.size() - 1], max_memory[max_memory.size() - 1]); } + +uint16_t VoxelGraphRuntime::get_output_port_address(ProgramGraph::PortLocation port) const { + const uint16_t *aptr = _output_port_addresses.getptr(port); + ERR_FAIL_COND_V(aptr == nullptr, 0); + return *aptr; +} + +float VoxelGraphRuntime::get_memory_value(uint16_t address) const { + CRASH_COND(address >= _memory.size()); + return _memory[address]; +} diff --git a/generators/graph/voxel_graph_runtime.h b/generators/graph/voxel_graph_runtime.h index c1523a74..4f27c2b1 100644 --- a/generators/graph/voxel_graph_runtime.h +++ b/generators/graph/voxel_graph_runtime.h @@ -15,12 +15,18 @@ public: float generate_single(const Vector3i &position); Interval analyze_range(Vector3i min_pos, Vector3i max_pos); + // Debugging + uint16_t get_output_port_address(ProgramGraph::PortLocation port) const; + float get_memory_value(uint16_t address) const; + private: std::vector _program; std::vector _memory; uint32_t _xzy_program_start; int _last_x; int _last_z; + + HashMap _output_port_addresses; }; #endif // VOXEL_GRAPH_RUNTIME_H diff --git a/math/interval.h b/math/interval.h index 2b672a88..79d2e15a 100644 --- a/math/interval.h +++ b/math/interval.h @@ -5,7 +5,6 @@ // For interval arithmetic struct Interval { - // Both inclusive float min; float max; diff --git a/util/utility.h b/util/utility.h index f486931c..e1f6cd46 100644 --- a/util/utility.h +++ b/util/utility.h @@ -50,6 +50,8 @@ inline void unordered_remove_if(std::vector &vec, F predicate) { if (predicate(vec[i])) { vec[i] = vec.back(); vec.pop_back(); + // Note: can underflow, but it should be fine since it's incremented right after. + // TODO Use a while()? --i; } }